#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "bits.h"
#include "cta.h"
#include "log.h"
#include "edid.h"
#include "displayid.h"

/**
 * Number of bytes in the CTA header (tag + revision + DTD offset + flags).
 */
#define CTA_HEADER_SIZE 4
/**
 * Exclusive upper bound for the detailed timing definitions in the CTA block.
 */
#define CTA_DTD_END 127
/**
 * Number of bytes in a CTA short audio descriptor.
 */
#define CTA_SAD_SIZE 3
/**
 * Number of bytes in a HDMI 3D audio descriptor.
 */
#define CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE 4
/**
 * IEEE Organizationally unique identifiers
 */
#define IEEE_OUI_DOLBY 0x00D046
#define IEEE_OUI_HDR10PLUS 0x90848B
#define IEEE_OUI_HDMI 0x000C03
#define IEEE_OUI_HDMI_FORUM 0xC45DD8

static void
add_failure(struct di_cta *cta, const char fmt[], ...)
{
	va_list args;

	va_start(args, fmt);
	_di_logger_va_add_failure(cta->logger, fmt, args);
	va_end(args);
}

static void
add_failure_until(struct di_cta *cta, int revision, const char fmt[], ...)
{
	va_list args;

	if (cta->revision > revision) {
		return;
	}

	va_start(args, fmt);
	_di_logger_va_add_failure(cta->logger, fmt, args);
	va_end(args);
}

static struct di_cta_svd *
parse_svd(struct di_cta *cta, uint8_t raw, uint8_t original_index,
	  const char *prefix)
{
	struct di_cta_svd svd, *svd_ptr;

	if (raw == 0 || raw == 128 || raw >= 254) {
		/* Reserved */
		add_failure_until(cta, 3,
				  "%s: Unknown VIC %" PRIu8 ".",
				  prefix,
				  raw);
		return NULL;
	} else if (raw <= 127 || raw >= 193) {
		svd = (struct di_cta_svd) {
			.vic = { .code = raw },
			.original_index = original_index,
		};
	} else {
		svd = (struct di_cta_svd) {
			.vic = { .code = get_bit_range(raw, 6, 0) },
			.native = true,
			.original_index = original_index,
		};
	}

	svd_ptr = calloc(1, sizeof(*svd_ptr));
	if (!svd_ptr)
		return NULL;
	*svd_ptr = svd;
	return svd_ptr;
}

static int
parse_video_block(struct di_cta *cta, struct di_cta_video_block_priv *video,
		  const uint8_t *data, size_t size)
{
	uint8_t i;
	struct di_cta_svd *svd;

	if (size == 0) {
		add_failure(cta, "Video Data Block: Empty Data Block");
		return -EINVAL;
	}

	assert(size < (1 << 8));

	for (i = 0; i < size; i++) {
		svd = parse_svd(cta, data[i], i, "Video Data Block");
		if (!svd)
			continue;
		assert(video->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES);
		video->svds[video->svds_len++] = svd;
	}

	video->base.svds = (const struct di_cta_svd *const *)video->svds;
	return 0;
}

static int
hdmi_latency_from_raw(struct di_cta *cta, const char *block_name,
		      const char *type, uint8_t raw)
{
	/* Unknown latency.  */
	if (raw == 0)
		return 0;

	/* Audio/video not supported. */
	if (raw == 255)
		return 0;

	if (raw > 251) {
		add_failure(cta,
			    "%s: %s latency byte is %u, but the ceil supported by spec is 251.",
			    block_name, type, raw);
		return 0;
	}

	return 2 * (raw - 1);
}

static int
parse_vendor_hdmi_block(struct di_cta *cta,
			struct di_cta_vendor_hdmi_block_priv *priv,
			const uint8_t *data, size_t size)
{
	const ssize_t offset = -1; /* Spec gives offset relative to header */
	const char block_name[] = "Vendor-Specific Data Block (HDMI), OUI 00-0C-03";
	struct di_cta_vendor_hdmi_block *block = &priv->base;
	size_t len_vic;
	size_t index;
	size_t i;
	uint8_t val;

	if (size < 5) {
		add_failure(cta, "%s: Empty Data Block", block_name);
		return -EINVAL;
	}

	block->source_phys_addr = (uint16_t)((uint16_t)data[4 + offset] << 8 | data[5 + offset]);

	if (size < 6)
		return 0;

	block->supports_ai = has_bit(data[6 + offset], 7);
	block->supports_dc_48bit = has_bit(data[6 + offset], 6);
	block->supports_dc_36bit = has_bit(data[6 + offset], 5);
	block->supports_dc_30bit = has_bit(data[6 + offset], 4);
	block->supports_dc_y444 = has_bit(data[6 + offset], 3);
	if (get_bit_range(data[6 + offset], 2, 1) != 0)
		add_failure(cta, "%s: Bits 2 and 1 of byte 6 are reserved.", block_name);
	block->supports_dvi_dual = has_bit(data[6 + offset], 0);

	if (size < 7)
		return 0;

	block->max_tmds_clock = data[7 + offset] * 5;

	if (size < 8)
		return 0;

	block->supports_content_game = has_bit(data[8 + offset], 3);
	block->supports_content_cinema = has_bit(data[8 + offset], 2);
	block->supports_content_photo = has_bit(data[8 + offset], 1);
	block->supports_content_graphics = has_bit(data[8 + offset], 0);

	block->has_latency = has_bit(data[8 + offset], 7);
	block->has_interlaced_latency = has_bit(data[8 + offset], 6);
	/* Bit 5 is reserved on older HDMI spec versions but appears as the
	 * HDMI_Video_present flag on newer ones. This flag is a bit useless,
	 * because it tells us if extended video details are provided, but the
	 * blob size itself already gives us this info and this approach works
	 * for all versions. Let's ignore bit 5. */
	if (has_bit(data[8 + offset], 4))
		add_failure(cta, "%s: Bit 4 of byte 8 is reserved.", block_name);

	if (block->has_interlaced_latency && !block->has_latency) {
		add_failure(cta,
			    "%s: Interlaced Latency support flag set, but Latency support flag is not",
			    block_name);
		return 0;
	}

	/* The next features from the block do not have fixed position, so we
	 * need to iterate simply incrementing the index. The next is byte 9. */
	index = (size_t) (9 + offset);

	if (block->has_latency) {
		if (size <= index + 1) { /* we need 2 bytes */
			add_failure(cta,
				    "%s: Latency support flag set, but bytes are missing",
				    block_name);
			return 0;
		}

		val = data[index++];
		block->supports_progressive_video = (val != 255);
		block->progressive_video_latency =
			hdmi_latency_from_raw(cta, block_name, "Video", val);

		val = data[index++];
		block->supports_progressive_audio = (val != 255);
		block->progressive_audio_latency =
			hdmi_latency_from_raw(cta, block_name, "Audio", val);
	}

	if (block->has_interlaced_latency) {
		if (size <= index + 1) { /* we need 2 bytes */
			add_failure(cta,
				    "%s: Interlaced Latency support flag set, but bytes are missing",
				    block_name);
			return 0;
		}

		val = data[index++];
		block->supports_interlaced_video = (val != 255);
		block->interlaced_video_latency =
			hdmi_latency_from_raw(cta, block_name, "Interlaced Video", val);

		val = data[index++];
		block->supports_interlaced_audio = (val != 255);
		block->interlaced_audio_latency =
			hdmi_latency_from_raw(cta, block_name, "Interlaced Audio", val);
	}

	if (size <= index)
		return 0;

	/* Skip a byte, it should only be used when we decode HDMI 3D VIC */
	index++;

	if (size <= index)
		return 0;

	len_vic = get_bit_range(data[index++], 7, 5);
	if (len_vic == 0) {
		add_failure(cta,
			    "%s: Extended Video Details flag but HDMI VIC list size 0",
			    block_name);
		return 0;
	}

	if (size <= index + len_vic - 1) {
		add_failure(cta,
			    "%s: HDMI VIC list size %u does not fit block of size %u",
			    block_name, len_vic, size);
		len_vic = size - index;
	}

	priv->vics = calloc(len_vic, sizeof(*priv->vics));
	if (!priv->vics)
		return -errno;

	for (i = 0; i < len_vic; i++) {
		val = data[index++];
		if (val < 1 || val > 4) {
			add_failure(cta,
				    "%s: HDMI VIC %d is invalid", block_name, val);
			continue;
		}
		priv->vics[block->vics_len++] = (struct di_hdmi_vic) { .code = val, };
	}
	block->vics = priv->vics;

	/* TODO: parse HDMI 3D VIC */

	return 0;
}

static int
parse_hdmi_scds(struct di_cta *cta, struct di_cta_hdmi_scds *scds,
		struct di_cta_hdmi_dsc *dsc, const uint8_t *data, size_t size,
		const char *block_name)
{
	const ssize_t offset = -1; /* Spec gives offset relative to header */
	uint8_t max_frl_rate;
	uint8_t max_slices;
	size_t i;

	if (size < 7) {
		add_failure(cta, "%s: Empty Data Block", block_name);
		return -EINVAL;
	}

	scds->version = data[4 + offset];
	if (scds->version != 1) {
		add_failure(cta, "%s: Unsupported version %d.", block_name, scds->version);
		return -ENOTSUP;
	}

	scds->max_tmds_char_rate_mhz = 5 * data[5 + offset];
	if (scds->max_tmds_char_rate_mhz != 0 && scds->max_tmds_char_rate_mhz <= 340)
		add_failure(cta, "%s: Max TMDS rate is != 0 and <= 340.", block_name);

	scds->supports_3d_osd_disparity = has_bit(data[6 + offset], 0);
	scds->supports_3d_dual_view = has_bit(data[6 + offset], 1);
	scds->supports_3d_independent_view = has_bit(data[6 + offset], 2);
	scds->supports_lte_340mcsc_scramble = has_bit(data[6 + offset], 3);
	scds->supports_ccbpci = has_bit(data[6 + offset], 4);
	scds->supports_cable_status = has_bit(data[6 + offset], 5);
	scds->supports_scdc_read_request = has_bit(data[6 + offset], 6);
	scds->supports_scdc = has_bit(data[6 + offset], 7);
	scds->supports_dc_30bit_420 = has_bit(data[7 + offset], 0);
	scds->supports_dc_36bit_420 = has_bit(data[7 + offset], 1);
	scds->supports_dc_48bit_420 = has_bit(data[7 + offset], 2);
	scds->supports_uhd_vic = has_bit(data[7 + offset], 3);

	max_frl_rate = get_bit_range(data[7 + offset], 7, 4);
	switch (max_frl_rate) {
	case DI_CTA_HDMI_FRL_UNSUPPORTED:
	case DI_CTA_HDMI_FRL_3GBPS_3LANES:
	case DI_CTA_HDMI_FRL_6GBPS_3LANES:
	case DI_CTA_HDMI_FRL_6GBPS_4LANES:
	case DI_CTA_HDMI_FRL_8GBPS_4LANES:
	case DI_CTA_HDMI_FRL_10GBPS_4LANES:
	case DI_CTA_HDMI_FRL_12GBPS_4LANES:
		scds->max_frl_rate = max_frl_rate;
		break;
	default:
		add_failure(cta, "%s: Unknown Max Fixed Rate Link (0x%02x).",
			    block_name, max_frl_rate);
		break;
	}

	if (max_frl_rate == DI_CTA_HDMI_FRL_3GBPS_3LANES &&
	    scds->max_tmds_char_rate_mhz < 300) {
		add_failure(cta, "%s: Max Fixed Rate Link is 1, but Max TMDS rate < 300.",
			    block_name);
	}
	if (max_frl_rate >= DI_CTA_HDMI_FRL_6GBPS_3LANES &&
	    scds->max_tmds_char_rate_mhz != 600) {
		add_failure(cta, "%s: Max Fixed Rate Link is >= 2, but Max TMDS rate != 600.",
			    block_name);
	}

	if (size < 8)
		return 0;

	scds->supports_fapa_start_location = has_bit(data[8 + offset], 0);
	scds->supports_allm = has_bit(data[8 + offset], 1);
	scds->supports_fva = has_bit(data[8 + offset], 2);
	scds->supports_neg_mvrr = has_bit(data[8 + offset], 3);
	scds->supports_cinema_vrr = has_bit(data[8 + offset], 4);
	if (scds->supports_cinema_vrr)
		add_failure(cta, "%s: CinemaVRR is deprecated and must be cleared.", block_name);
	scds->m_delta = has_bit(data[8 + offset], 5);
	scds->supports_qms = has_bit(data[8 + offset], 6);
	scds->supports_fapa_end_extended = has_bit(data[8 + offset], 7);

	if (size < 10)
		return 0;

	scds->vrr_min_hz = get_bit_range(data[9 + offset], 5, 0);
	scds->vrr_max_hz = (get_bit_range(data[9 + offset], 7, 6) << 8) | data[10 + offset];

	if (scds->vrr_min_hz > 48)
		add_failure(cta, "%s: VRRmin > 48.", block_name);
	if (scds->vrr_min_hz == 0 && scds->vrr_max_hz != 0)
		add_failure(cta, "%s: VRRmin == 0, but VRRmax isn't.", block_name);
	if (scds->vrr_max_hz < 100)
		add_failure(cta, "%s: VRRmax < 100.", block_name);

	if (size < 13)
		return 0;

	dsc->supports_10bpc = has_bit(data[11 + offset], 0);
	dsc->supports_12bpc = has_bit(data[11 + offset], 1);
	dsc->supports_all_bpc = has_bit(data[11 + offset], 3);

	scds->qms_tfr_min = has_bit(data[11 + offset], 4);
	scds->qms_tfr_max = has_bit(data[11 + offset], 5);

	if (scds->qms_tfr_min && !scds->supports_qms)
		add_failure(cta, "%s: QMS_TFR_min is set but QMS is not.", block_name);
	if (scds->qms_tfr_max && !scds->supports_qms)
		add_failure(cta, "%s: QMS_TFR_max is set but QMS is not.", block_name);

	dsc->supports_native_420 = has_bit(data[11 + offset], 6);
	if (has_bit(data[11 + offset], 2))
		add_failure(cta, "%s: DSC_16bpc bit is reserved.", block_name);
	if (get_bit_range(data[11 + offset], 5, 4) != 0)
		add_failure(cta, "%s: Bits 4 and 5 of byte 11 are reserved.", block_name);

	max_slices = get_bit_range(data[12 + offset], 3, 0);
	switch (max_slices) {
	case DI_CTA_HDMI_DSC_MAX_SLICES_UNSUPPORTED:
	case DI_CTA_HDMI_DSC_MAX_SLICES_1_340MHZ:
	case DI_CTA_HDMI_DSC_MAX_SLICES_2_340MHZ:
	case DI_CTA_HDMI_DSC_MAX_SLICES_4_340MHZ:
	case DI_CTA_HDMI_DSC_MAX_SLICES_8_340MHZ:
	case DI_CTA_HDMI_DSC_MAX_SLICES_8_400MHZ:
	case DI_CTA_HDMI_DSC_MAX_SLICES_12_400MHZ:
	case DI_CTA_HDMI_DSC_MAX_SLICES_16_400MHZ:
		dsc->max_slices = max_slices;
		break;
	default:
		add_failure(cta, "%s: Unknown DSC Max Slices (0x%02x).",
			    block_name, max_slices);
		break;
	}

	max_frl_rate = get_bit_range(data[12 + offset], 7, 4);
	switch (max_frl_rate) {
	case DI_CTA_HDMI_FRL_UNSUPPORTED:
	case DI_CTA_HDMI_FRL_3GBPS_3LANES:
	case DI_CTA_HDMI_FRL_6GBPS_3LANES:
	case DI_CTA_HDMI_FRL_6GBPS_4LANES:
	case DI_CTA_HDMI_FRL_8GBPS_4LANES:
	case DI_CTA_HDMI_FRL_10GBPS_4LANES:
	case DI_CTA_HDMI_FRL_12GBPS_4LANES:
		dsc->max_frl_rate = max_frl_rate;
		break;
	default:
		add_failure(cta, "%s: Unknown Max Fixed Rate Link (0x%02x).",
			    block_name, max_frl_rate);
		break;
	}

	dsc->max_total_chunk_bytes = 1024 * (1 + get_bit_range(data[13 + offset], 5, 0));
	if (get_bit_range(data[13 + offset], 7, 6) != 0)
		add_failure(cta, "%s: Bits 6 and 7 of byte 13 are reserved.", block_name);

	if (has_bit(data[11 + offset], 7))
		scds->dsc = dsc;
	else if (data[11 + offset] != 0 || data[12 + offset] != 0 || data[13 + offset] != 0) {
		add_failure(cta, "%s: DSC_1p2 is unset but DSC bits are not zero.", block_name);
	}

	for (i = 13; i < size; i++) {
		if (data[i] != 0)
			add_failure(cta, "%s: Byte %d is reserved.", block_name);
	}

	return 0;
}

static int
parse_vendor_hdmi_forum_block(struct di_cta *cta,
			      struct di_cta_vendor_hdmi_forum_block_priv *priv,
			      const uint8_t *data, size_t size)
{
	struct di_cta_vendor_hdmi_forum_block *block = &priv->base;
	struct di_cta_hdmi_scds *scds = &block->scds;
	struct di_cta_hdmi_dsc *dsc = &priv->dsc;
	const char *block_name;

	block_name = "Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8";

	return parse_hdmi_scds(cta, scds, dsc, data, size, block_name);
}

static int
parse_hdmi_forum_sink_cap(struct di_cta *cta,
			  struct di_cta_hdmi_forum_sink_cap_priv *priv,
			  const uint8_t *data, size_t size)
{
	struct di_cta_hdmi_forum_sink_cap *block = &priv->base;
	struct di_cta_hdmi_scds *scds = &block->scds;
	struct di_cta_hdmi_dsc *dsc = &priv->dsc;
	const char *block_name;

	block_name = "HDMI Forum Sink Capability Data Block";

	return parse_hdmi_scds(cta, scds, dsc, data, size, block_name);
}

static int
parse_ycbcr420_block(struct di_cta *cta,
		     struct di_cta_ycbcr420_video_block_priv *ycbcr420,
		     const uint8_t *data, size_t size)
{
	uint8_t i;
	struct di_cta_svd *svd;

	if (size == 0) {
		add_failure(cta, "YCbCr 4:2:0 Video Data Block: Empty Data Block");
		return -EINVAL;
	}

	assert(size < (1 << 8));

	for (i = 0; i < size; i++) {
		svd = parse_svd(cta, data[i], i, "YCbCr 4:2:0 Video Data Block");
		if (!svd)
			continue;
		assert(ycbcr420->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES);
		ycbcr420->svds[ycbcr420->svds_len++] = svd;
	}

	ycbcr420->base.svds = (const struct di_cta_svd *const *)ycbcr420->svds;
	return 0;
}

static bool
parse_sad_format(struct di_cta *cta, uint8_t code, uint8_t code_ext,
		 enum di_cta_audio_format *format, const char *prefix)
{
	switch (code) {
	case 0x0:
		add_failure_until(cta, 3, "%s: Audio Format Code 0x00 is reserved.", prefix);
		return false;
	case 0x1:
		*format = DI_CTA_AUDIO_FORMAT_LPCM;
		break;
	case 0x2:
		*format = DI_CTA_AUDIO_FORMAT_AC3;
		break;
	case 0x3:
		*format = DI_CTA_AUDIO_FORMAT_MPEG1;
		break;
	case 0x4:
		*format = DI_CTA_AUDIO_FORMAT_MP3;
		break;
	case 0x5:
		*format = DI_CTA_AUDIO_FORMAT_MPEG2;
		break;
	case 0x6:
		*format = DI_CTA_AUDIO_FORMAT_AAC_LC;
		break;
	case 0x7:
		*format = DI_CTA_AUDIO_FORMAT_DTS;
		break;
	case 0x8:
		*format = DI_CTA_AUDIO_FORMAT_ATRAC;
		break;
	case 0x9:
		*format = DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO;
		break;
	case 0xA:
		*format = DI_CTA_AUDIO_FORMAT_ENHANCED_AC3;
		break;
	case 0xB:
		*format = DI_CTA_AUDIO_FORMAT_DTS_HD;
		break;
	case 0xC:
		*format = DI_CTA_AUDIO_FORMAT_MAT;
		break;
	case 0xD:
		*format = DI_CTA_AUDIO_FORMAT_DST;
		break;
	case 0xE:
		*format = DI_CTA_AUDIO_FORMAT_WMA_PRO;
		break;
	case 0xF:
		switch (code_ext) {
		case 0x04:
			*format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC;
			break;
		case 0x05:
			*format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2;
			break;
		case 0x06:
			*format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC;
			break;
		case 0x07:
			*format = DI_CTA_AUDIO_FORMAT_DRA;
			break;
		case 0x08:
			*format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND;
			break;
		case 0x0A:
			*format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND;
			break;
		case 0x0B:
			*format = DI_CTA_AUDIO_FORMAT_MPEGH_3D;
			break;
		case 0x0C:
			*format = DI_CTA_AUDIO_FORMAT_AC4;
			break;
		case 0x0D:
			*format = DI_CTA_AUDIO_FORMAT_LPCM_3D;
			break;
		default:
			add_failure_until(cta, 3, "%s: Unknown Audio Ext Format 0x%02x.",
					  prefix, code_ext);
			return false;
		}
		break;
	default:
		add_failure_until(cta, 3, "%s: Unknown Audio Format 0x%02x.", prefix, code);
		return false;
	}

	return true;
}

static int
parse_sad(struct di_cta *cta, struct di_cta_audio_block_priv *audio,
	  const uint8_t data[static CTA_SAD_SIZE])
{
	enum di_cta_audio_format format;
	struct di_cta_sad_priv *priv;
	struct di_cta_sad *sad;
	struct di_cta_sad_sample_rates *sample_rates;
	struct di_cta_sad_lpcm *lpcm;
	struct di_cta_sad_mpegh_3d *mpegh_3d;
	struct di_cta_sad_mpeg_aac *mpeg_aac;
	struct di_cta_sad_mpeg_surround *mpeg_surround;
	struct di_cta_sad_mpeg_aac_le *mpeg_aac_le;
	struct di_cta_sad_enhanced_ac3 *enhanced_ac3;
	struct di_cta_sad_mat *mat;
	struct di_cta_sad_wma_pro *wma_pro;
	uint8_t code, code_ext;

	code = get_bit_range(data[0], 6, 3);
	code_ext = get_bit_range(data[2], 7, 3);

	if (!parse_sad_format(cta, code, code_ext, &format, "Audio Data Block"))
		return -EINVAL;

	priv = calloc(1, sizeof(*priv));
	if (!priv)
		return -errno;

	sad = &priv->base;
	sample_rates = &priv->supported_sample_rates;
	lpcm = &priv->lpcm;
	mpegh_3d = &priv->mpegh_3d;
	mpeg_aac = &priv->mpeg_aac;
	mpeg_surround = &priv->mpeg_surround;
	mpeg_aac_le = &priv->mpeg_aac_le;
	enhanced_ac3 = &priv->enhanced_ac3;
	mat = &priv->mat;
	wma_pro = &priv->wma_pro;

	sad->format = format;

	/* TODO: Find DRA documentation */

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_LPCM:
	case DI_CTA_AUDIO_FORMAT_AC3:
	case DI_CTA_AUDIO_FORMAT_MPEG1:
	case DI_CTA_AUDIO_FORMAT_MP3:
	case DI_CTA_AUDIO_FORMAT_MPEG2:
	case DI_CTA_AUDIO_FORMAT_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_DTS:
	case DI_CTA_AUDIO_FORMAT_ATRAC:
	case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
	case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3:
	case DI_CTA_AUDIO_FORMAT_DTS_HD:
	case DI_CTA_AUDIO_FORMAT_MAT:
	case DI_CTA_AUDIO_FORMAT_DST:
	case DI_CTA_AUDIO_FORMAT_WMA_PRO:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
	/* DRA is not documented but this is what edid-decode does */
	case DI_CTA_AUDIO_FORMAT_DRA:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
		sad->max_channels = get_bit_range(data[0], 2, 0) + 1;
		break;
	case DI_CTA_AUDIO_FORMAT_LPCM_3D:
		sad->max_channels = (get_bit_range(data[0], 2, 0) |
				     (get_bit_range(data[0], 7, 7) << 3) |
				     (get_bit_range(data[1], 7, 7) << 4)) + 1;
		break;
	case DI_CTA_AUDIO_FORMAT_MPEGH_3D:
	case DI_CTA_AUDIO_FORMAT_AC4:
		break;
	}

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_LPCM:
	case DI_CTA_AUDIO_FORMAT_AC3:
	case DI_CTA_AUDIO_FORMAT_MPEG1:
	case DI_CTA_AUDIO_FORMAT_MP3:
	case DI_CTA_AUDIO_FORMAT_MPEG2:
	case DI_CTA_AUDIO_FORMAT_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_DTS:
	case DI_CTA_AUDIO_FORMAT_ATRAC:
	case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
	case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3:
	case DI_CTA_AUDIO_FORMAT_DTS_HD:
	case DI_CTA_AUDIO_FORMAT_MAT:
	case DI_CTA_AUDIO_FORMAT_DST:
	case DI_CTA_AUDIO_FORMAT_WMA_PRO:
	/* DRA is not documented but this is what edid-decode does */
	case DI_CTA_AUDIO_FORMAT_DRA:
	case DI_CTA_AUDIO_FORMAT_MPEGH_3D:
	case DI_CTA_AUDIO_FORMAT_LPCM_3D:
		sample_rates->has_192_khz = has_bit(data[1], 6);
		sample_rates->has_176_4_khz = has_bit(data[1], 5);
		/* fallthrough */
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
		sample_rates->has_96_khz = has_bit(data[1], 4);
		sample_rates->has_88_2_khz = has_bit(data[1], 3);
		sample_rates->has_48_khz = has_bit(data[1], 2);
		sample_rates->has_44_1_khz = has_bit(data[1], 1);
		sample_rates->has_32_khz = has_bit(data[1], 0);
		break;
	case DI_CTA_AUDIO_FORMAT_AC4:
		sample_rates->has_192_khz = has_bit(data[1], 6);
		sample_rates->has_96_khz = has_bit(data[1], 4);
		sample_rates->has_48_khz = has_bit(data[1], 2);
		sample_rates->has_44_1_khz = has_bit(data[1], 1);
		break;
	}
	sad->supported_sample_rates = sample_rates;

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_AC3:
	case DI_CTA_AUDIO_FORMAT_MPEG1:
	case DI_CTA_AUDIO_FORMAT_MP3:
	case DI_CTA_AUDIO_FORMAT_MPEG2:
	case DI_CTA_AUDIO_FORMAT_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_DTS:
	case DI_CTA_AUDIO_FORMAT_ATRAC:
		sad->max_bitrate_kbs = data[2] * 8;
		break;
	default:
		break;
	}

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_LPCM:
	case DI_CTA_AUDIO_FORMAT_LPCM_3D:
		lpcm->has_sample_size_24_bits = has_bit(data[2], 2);
		lpcm->has_sample_size_20_bits = has_bit(data[2], 1);
		lpcm->has_sample_size_16_bits = has_bit(data[2], 0);
		sad->lpcm = lpcm;
	default:
		break;
	}

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
		mpeg_aac->has_frame_length_1024 = has_bit(data[2], 2);
		mpeg_aac->has_frame_length_960 = has_bit(data[2], 1);
		sad->mpeg_aac = mpeg_aac;
		break;
	default:
		break;
	}

	if (format == DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC) {
		mpeg_aac_le->supports_multichannel_sound = has_bit(data[2], 0);
		sad->mpeg_aac_le = mpeg_aac_le;
	}

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
		mpeg_surround->signaling = has_bit(data[2], 0);
		sad->mpeg_surround = mpeg_surround;
		break;
	default:
		break;
	}

	if (format == DI_CTA_AUDIO_FORMAT_MPEGH_3D) {
		mpegh_3d->low_complexity_profile = has_bit(data[2], 0);
		mpegh_3d->baseline_profile = has_bit(data[2], 1);
		mpegh_3d->level = get_bit_range(data[0], 2, 0);
		if (mpegh_3d->level > DI_CTA_SAD_MPEGH_3D_LEVEL_5) {
			add_failure_until(cta, 3,
					  "Unknown MPEG-H 3D Audio Level 0x%02x.",
					  mpegh_3d->level);
			mpegh_3d->level = DI_CTA_SAD_MPEGH_3D_LEVEL_UNSPECIFIED;
		}
		sad->mpegh_3d = mpegh_3d;
	}

	if (format == DI_CTA_AUDIO_FORMAT_ENHANCED_AC3) {
		enhanced_ac3->supports_joint_object_coding =
			has_bit(data[2], 0);
		enhanced_ac3->supports_joint_object_coding_ACMOD28 =
			has_bit(data[2], 1);
		sad->enhanced_ac3 = enhanced_ac3;
	}

	if (format == DI_CTA_AUDIO_FORMAT_MAT) {
		mat->supports_object_audio_and_channel_based =
			has_bit(data[2], 0);
		if (mat->supports_object_audio_and_channel_based)
			mat->requires_hash_calculation = !has_bit(data[2], 0);
		sad->mat = mat;
	}

	if (format == DI_CTA_AUDIO_FORMAT_WMA_PRO) {
		wma_pro->profile = get_bit_range(data[2], 2, 0);
		sad->wma_pro = wma_pro;
	}

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
	case DI_CTA_AUDIO_FORMAT_DTS_HD:
	case DI_CTA_AUDIO_FORMAT_DST:
		/* TODO data[2] 7:0 contains unknown Audio Format Code dependent value */
		break;
	default:
		break;
	}

	if (format == DI_CTA_AUDIO_FORMAT_AC4) {
		/* TODO data[2] 2:0 contains unknown Audio Format Code dependent value */
	}

	switch (format) {
	case DI_CTA_AUDIO_FORMAT_LPCM:
	case DI_CTA_AUDIO_FORMAT_WMA_PRO:
		if (has_bit(data[0], 7) || has_bit(data[1], 7) ||
		    get_bit_range(data[2], 7, 3) != 0)
			add_failure_until(cta, 3,
					  "Bits F17, F27, F37:F33 must be 0.");
		break;
	case DI_CTA_AUDIO_FORMAT_AC3:
	case DI_CTA_AUDIO_FORMAT_MPEG1:
	case DI_CTA_AUDIO_FORMAT_MP3:
	case DI_CTA_AUDIO_FORMAT_MPEG2:
	case DI_CTA_AUDIO_FORMAT_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_DTS:
	case DI_CTA_AUDIO_FORMAT_ATRAC:
	case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
	case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3:
	case DI_CTA_AUDIO_FORMAT_DTS_HD:
	case DI_CTA_AUDIO_FORMAT_MAT:
	case DI_CTA_AUDIO_FORMAT_DST:
		if (has_bit(data[0], 7) || has_bit(data[1], 7))
			add_failure_until(cta, 3,
					  "Bits F17, F27 must be 0.");
		break;
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
	case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
	case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
		if (has_bit(data[0], 7) || get_bit_range(data[2], 7, 5) != 0)
			add_failure_until(cta, 3,
					  "Bits F17, F27:F25 must be 0.");
		break;
	case DI_CTA_AUDIO_FORMAT_MPEGH_3D:
		if (has_bit(data[0], 7) || has_bit(data[1], 7) ||
		    has_bit(data[2], 2))
			add_failure_until(cta, 3,
					  "Bits F17, F27, F32 must be 0.");
		break;
	case DI_CTA_AUDIO_FORMAT_AC4:
		if ((data[0] & 0x87) != 0 || (data[1] & 0xA9) != 0)
			add_failure_until(cta, 3,
					  "Bits F17, F12:F10, F27, F25, F23, "
					  "F20 must be 0.");
		break;
	/* DRA documentation missing */
	case DI_CTA_AUDIO_FORMAT_DRA:
	case DI_CTA_AUDIO_FORMAT_LPCM_3D:
		break;
	}

	assert(audio->sads_len < EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES);
	audio->sads[audio->sads_len++] = priv;
	return 0;
}

static int
parse_audio_block(struct di_cta *cta, struct di_cta_audio_block_priv *audio,
		  const uint8_t *data, size_t size)
{
	size_t i;
	int ret;

	if (size % 3 != 0)
		add_failure(cta, "Broken CTA-861 audio block length %d.", size);

	for (i = 0; i + 3 <= size; i += 3) {
		ret = parse_sad(cta, audio, &data[i]);
		if (ret != 0 && ret != -EINVAL)
			return ret;
	}

	if (audio->sads_len == 0)
		return -EINVAL;

	audio->audio.sads = (const struct di_cta_sad *const *) audio->sads;
	return 0;
}

static bool
parse_speaker_alloc(struct di_cta *cta, struct di_cta_speaker_allocation *speaker_alloc,
		    const uint8_t data[3], const char *prefix)
{
	bool rlc_rrc;

	speaker_alloc->flw_frw = has_bit(data[0], 7);
	rlc_rrc = has_bit(data[0], 6);
	speaker_alloc->flc_frc = has_bit(data[0], 5);
	speaker_alloc->bc = has_bit(data[0], 4);
	speaker_alloc->bl_br = has_bit(data[0], 3);
	speaker_alloc->fc = has_bit(data[0], 2);
	speaker_alloc->lfe1 = has_bit(data[0], 1);
	speaker_alloc->fl_fr = has_bit(data[0], 0);
	if (rlc_rrc) {
		if (cta->revision >= 3)
			add_failure(cta, "%s: Deprecated bit F16 must be 0.", prefix);
		else
			speaker_alloc->bl_br = true;
	}

	speaker_alloc->tpsil_tpsir = has_bit(data[1], 7);
	speaker_alloc->sil_sir = has_bit(data[1], 6);
	speaker_alloc->tpbc = has_bit(data[1], 5);
	speaker_alloc->lfe2 = has_bit(data[1], 4);
	speaker_alloc->ls_rs = has_bit(data[1], 3);
	speaker_alloc->tpfc = has_bit(data[1], 2);
	speaker_alloc->tpc = has_bit(data[1], 1);
	speaker_alloc->tpfl_tpfr = has_bit(data[1], 0);

	if (get_bit_range(data[2], 7, 4) != 0)
		add_failure(cta, "%s: Bits F37, F36, F34 must be 0.", prefix);
	if (cta->revision >= 3 && has_bit(data[2], 3))
		add_failure(cta, "%s: Deprecated bit F33 must be 0.", prefix);
	speaker_alloc->btfl_btfr = has_bit(data[2], 2);
	speaker_alloc->btfc = has_bit(data[2], 1);
	speaker_alloc->tpbl_tpbr = has_bit(data[2], 0);

	return true;
}

static int
parse_speaker_alloc_block(struct di_cta *cta,
			  struct di_cta_speaker_alloc_block *speaker_alloc,
			  const uint8_t *data, size_t size)
{
	if (size < 3) {
		add_failure(cta,
			    "Speaker Allocation Data Block: Empty Data Block with length %zu.",
			    size);
		return -EINVAL;
	}

	parse_speaker_alloc(cta, &speaker_alloc->speakers, data,
			    "Speaker Allocation Data Block");

	return 0;
}

static int
parse_video_cap_block(struct di_cta *cta,
		      struct di_cta_video_cap_block *video_cap,
		      const uint8_t *data, size_t size)
{
	if (size < 1) {
		add_failure(cta,
			    "Video Capability Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	video_cap->selectable_ycc_quantization_range = has_bit(data[0], 7);
	video_cap->selectable_rgb_quantization_range = has_bit(data[0], 6);
	video_cap->pt_over_underscan = get_bit_range(data[0], 5, 4);
	video_cap->it_over_underscan = get_bit_range(data[0], 3, 2);
	video_cap->ce_over_underscan = get_bit_range(data[0], 1, 0);

	if (!video_cap->selectable_rgb_quantization_range && cta->revision >= 3)
		add_failure(cta,
			    "Video Capability Data Block: Set Selectable RGB Quantization to avoid interop issues.");
	/* TODO: add failure if selectable_ycc_quantization_range is unset,
	 * the sink supports YCbCr formats and the revision is 3+ */

	switch (video_cap->it_over_underscan) {
	case DI_CTA_VIDEO_CAP_ALWAYS_OVERSCAN:
		if (cta->flags && cta->flags->it_underscan)
			add_failure(cta, "Video Capability Data Block: IT video formats are always overscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to underscanned.");
		break;
	case DI_CTA_VIDEO_CAP_ALWAYS_UNDERSCAN:
		if (cta->flags && !cta->flags->it_underscan)
			add_failure(cta, "Video Capability Data Block: IT video formats are always underscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to overscanned.");
	default:
		break;
	}

	return 0;
}

static bool
check_vesa_display_device_num_channels(enum di_cta_vesa_display_device_interface_type interface,
			     uint8_t num_channels)
{
	switch (interface) {
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_VGA:
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_V:
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_D:
		return num_channels == 0;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_LVDS:
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_RSDS:
		return true;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_D:
		return num_channels == 1 || num_channels == 2;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_ANALOG:
		return num_channels == 0;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_DIGITAL:
		return num_channels == 1 || num_channels == 2;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_A:
		return num_channels == 1;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_B:
		return num_channels == 2;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_MDDI:
		return num_channels == 1 || num_channels == 2;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DISPLAYPORT:
		return num_channels == 1 || num_channels == 2 || num_channels == 4;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_IEEE_1394:
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_ANALOG:
		return num_channels == 0;
	case DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_DIGITAL:
		return num_channels == 1 || num_channels == 2;
	}
	abort(); /* unreachable */
}

static void
parse_vesa_display_device_additional_primary_chromaticity(struct di_cta_vesa_display_device_additional_primary_chromaticity *coords,
						uint8_t low,
						const uint8_t high[static 2])
{
	uint16_t raw_x, raw_y; /* only 10 bits are used */

	raw_x = (uint16_t) ((high[0] << 2) | get_bit_range(low, 3, 2));
	raw_y = (uint16_t) ((high[1] << 2) | get_bit_range(low, 1, 0));

	*coords = (struct di_cta_vesa_display_device_additional_primary_chromaticity) {
		.x = (float) raw_x / 1024,
		.y = (float) raw_y / 1024,
	};
}

static int
parse_vesa_display_device(struct di_cta *cta, struct di_cta_vesa_display_device_block *dddb,
		const uint8_t *data, size_t size)
{
	const size_t offset = 2; /* CTA block header */
	uint8_t interface_type, num_channels, content_protection, scan_direction,
		subpixel_layout;

	if (size + offset != 32) {
		add_failure(cta, "VESA Video Display Device Data Block: Invalid length %u.", size);
		return -EINVAL;
	}

	interface_type = get_bit_range(data[0x02 - offset], 7, 4);
	num_channels = get_bit_range(data[0x02 - offset], 3, 0);
	switch (interface_type) {
	case 0x0: /* Analog */
		/* Special case: num_channels contains the detailed interface
		 * type. */
		switch (num_channels) {
		case 0x0:
			dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_VGA;
			break;
		case 0x1:
			dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_V;
			break;
		case 0x2:
			dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_NAVI_D;
			break;
		default:
			add_failure(cta,
				    "VESA Video Display Device Data Block: Unknown analog interface type 0x%x.",
				    num_channels);
			return -EINVAL;
		}
		num_channels = 0;
		break;
	case 0x1:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_LVDS;
		break;
	case 0x2:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_RSDS;
		break;
	case 0x3:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_D;
		break;
	case 0x4:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_ANALOG;
		break;
	case 0x5:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DVI_I_DIGITAL;
		break;
	case 0x6:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_A;
		break;
	case 0x7:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_HDMI_B;
		break;
	case 0x8:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_MDDI;
		break;
	case 0x9:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_DISPLAYPORT;
		break;
	case 0xA:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_IEEE_1394;
		break;
	case 0xB:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_ANALOG;
		break;
	case 0xC:
		dddb->interface_type = DI_CTA_VESA_DISPLAY_DEVICE_INTERFACE_M1_DIGITAL;
		break;
	default:
		add_failure(cta,
			    "VESA Video Display Device Data Block: Unknown interface type 0x%x.",
			    interface_type);
		return -EINVAL;
	}

	if (check_vesa_display_device_num_channels(dddb->interface_type, num_channels))
		dddb->num_channels = num_channels;
	else
		add_failure(cta,
			    "VESA Video Display Device Data Block: Invalid number of lanes/channels %u.",
			    num_channels);

	dddb->interface_version = get_bit_range(data[0x03 - offset], 7, 4);
	dddb->interface_release = get_bit_range(data[0x03 - offset], 3, 0);

	content_protection = data[0x04 - offset];
	switch (content_protection) {
	case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_NONE:
	case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_HDCP:
	case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_DTCP:
	case DI_CTA_VESA_DISPLAY_DEVICE_CONTENT_PROTECTION_DPCP:
		dddb->content_protection = content_protection;
		break;
	default:
		add_failure(cta,
			    "VESA Video Display Device Data Block: Invalid content protection 0x%x.",
			    content_protection);
	}

	dddb->min_clock_freq_mhz = get_bit_range(data[0x05 - offset], 7, 2);
	dddb->max_clock_freq_mhz =
		(get_bit_range(data[0x05 - offset], 1, 0) << 8) | data[0x06 - offset];
	if (dddb->min_clock_freq_mhz > dddb->max_clock_freq_mhz) {
		add_failure(cta,
			    "VESA Video Display Device Data Block: Minimum clock frequency (%d MHz) greater than maximum (%d MHz).",
			    dddb->min_clock_freq_mhz, dddb->max_clock_freq_mhz);
		dddb->min_clock_freq_mhz = dddb->max_clock_freq_mhz = 0;
	}

	dddb->native_horiz_pixels = data[0x07 - offset] | (data[0x08 - offset] << 8);
	dddb->native_vert_pixels = data[0x09 - offset] | (data[0x0A - offset] << 8);

	dddb->aspect_ratio = (float)data[0x0B - offset] / 100 + 1;
	dddb->default_orientation = get_bit_range(data[0x0C - offset], 7, 6);
	dddb->rotation_cap = get_bit_range(data[0x0C - offset], 5, 4);
	dddb->zero_pixel_location = get_bit_range(data[0x0C - offset], 3, 2);
	scan_direction = get_bit_range(data[0x0C - offset], 1, 0);
	if (scan_direction != 3)
		dddb->scan_direction = scan_direction;
	else
		add_failure(cta,
			   "VESA Video Display Device Data Block: Invalid scan direction 0x%x.",
			   scan_direction);

	subpixel_layout = data[0x0D - offset];
	switch (subpixel_layout) {
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_UNDEFINED:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_VERT:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_RGB_HORIZ:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_EDID_CHROM_VERT:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_EDID_CHROM_HORIZ:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_RGGB:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_GBRG:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_DELTA_RGB:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_MOSAIC:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_QUAD_ANY:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_FIVE:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_SIX:
	case DI_CTA_VESA_DISPLAY_DEVICE_SUBPIXEL_CLAIRVOYANTE_PENTILE:
		dddb->subpixel_layout = subpixel_layout;
		break;
	default:
		add_failure(cta,
			   "VESA Video Display Device Data Block: Invalid subpixel layout 0x%x.",
			   subpixel_layout);
	}

	dddb->horiz_pitch_mm = (float)data[0x0E - offset] * 0.01f;
	dddb->vert_pitch_mm = (float)data[0x0F - offset] * 0.01f;

	dddb->dithering_type = get_bit_range(data[0x10 - offset], 7, 6);
	dddb->direct_drive = has_bit(data[0x10 - offset], 5);
	dddb->overdrive_not_recommended = has_bit(data[0x10 - offset], 4);
	dddb->deinterlacing = has_bit(data[0x10 - offset], 3);
	if (get_bit_range(data[0x10 - offset], 2, 0) != 0)
		add_failure(cta, "VESA Video Display Device Data Block: Reserved miscellaneous display capabilities bits 2-0 must be 0.");

	dddb->audio_support = has_bit(data[0x11 - offset], 7);
	dddb->separate_audio_inputs = has_bit(data[0x11 - offset], 6);
	dddb->audio_input_override = has_bit(data[0x11 - offset], 5);
	if (get_bit_range(data[0x11 - offset], 4, 0) != 0)
		add_failure(cta, "VESA Video Display Device Data Block: Reserved audio bits 4-0 must be 0.");

	dddb->audio_delay_provided = data[0x12 - offset] != 0;
	dddb->audio_delay_ms = 2 * get_bit_range(data[0x12 - offset], 6, 0);
	if (!has_bit(data[0x12 - offset], 7))
		dddb->audio_delay_ms = -dddb->audio_delay_ms;

	dddb->frame_rate_conversion = get_bit_range(data[0x13 - offset], 7, 6);
	dddb->frame_rate_range_hz = get_bit_range(data[0x13 - offset], 5, 0);
	dddb->frame_rate_native_hz = data[0x14 - offset];

	dddb->bit_depth_interface = get_bit_range(data[0x15 - offset], 7, 4) + 1;
	dddb->bit_depth_display = get_bit_range(data[0x15 - offset], 3, 0) + 1;

	dddb->additional_primary_chromaticities_len = get_bit_range(data[0x17 - offset], 1, 0);
	parse_vesa_display_device_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[0],
							get_bit_range(data[0x16 - offset], 7, 4),
							&data[0x18 - offset]);
	parse_vesa_display_device_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[1],
							get_bit_range(data[0x16 - offset], 3, 0),
							&data[0x1A - offset]);
	parse_vesa_display_device_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[2],
							get_bit_range(data[0x17 - offset], 7, 4),
							&data[0x1C - offset]);
	if (get_bit_range(data[0x17 - offset], 3, 2) != 0)
		add_failure(cta, "VESA Video Display Device Data Block: Reserved additional primary chromaticities bits 3-2 of byte 0x17 must be 0.");

	dddb->resp_time_transition = has_bit(data[0x1E - offset], 7);
	dddb->resp_time_ms = get_bit_range(data[0x1E - offset], 6, 0);

	dddb->overscan_horiz_pct = get_bit_range(data[0x1F - offset], 7, 4);
	dddb->overscan_vert_pct = get_bit_range(data[0x1F - offset], 3, 0);

	return 0;
}

static int
parse_colorimetry_block(struct di_cta *cta,
			struct di_cta_colorimetry_block *colorimetry,
			const uint8_t *data, size_t size)
{
	if (size < 2) {
		add_failure(cta, "Colorimetry Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	colorimetry->bt2020_rgb = has_bit(data[0], 7);
	colorimetry->bt2020_ycc = has_bit(data[0], 6);
	colorimetry->bt2020_cycc = has_bit(data[0], 5);
	colorimetry->oprgb = has_bit(data[0], 4);
	colorimetry->opycc_601 = has_bit(data[0], 3);
	colorimetry->sycc_601 = has_bit(data[0], 2);
	colorimetry->xvycc_709 = has_bit(data[0], 1);
	colorimetry->xvycc_601 = has_bit(data[0], 0);

	colorimetry->st2113_rgb = has_bit(data[1], 7);
	colorimetry->ictcp = has_bit(data[1], 6);

	if (get_bit_range(data[1], 5, 0) != 0)
		add_failure_until(cta, 3,
				  "Colorimetry Data Block: Reserved bits MD0-MD3 must be 0.");

	return 0;
}

static float
parse_max_luminance(uint8_t raw)
{
	if (raw == 0)
		return 0;
	return 50 * powf(2, (float) raw / 32);
}

static float
parse_min_luminance(uint8_t raw, float max)
{
	if (raw == 0)
		return 0;
	return max * powf((float) raw / 255, 2) / 100;
}

static int
parse_hdr_static_metadata_block(struct di_cta *cta,
				struct di_cta_hdr_static_metadata_block_priv *metadata,
				const uint8_t *data, size_t size)
{
	uint8_t eotfs, descriptors;

	if (size < 2) {
		add_failure(cta, "HDR Static Metadata Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	metadata->base.eotfs = &metadata->eotfs;
	metadata->base.descriptors = &metadata->descriptors;

	eotfs = data[0];
	metadata->eotfs.traditional_sdr = has_bit(eotfs, 0);
	metadata->eotfs.traditional_hdr = has_bit(eotfs, 1);
	metadata->eotfs.pq = has_bit(eotfs, 2);
	metadata->eotfs.hlg = has_bit(eotfs, 3);
	if (get_bit_range(eotfs, 7, 4))
		add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown EOTF.");

	descriptors = data[1];
	metadata->descriptors.type1 = has_bit(descriptors, 0);
	if (get_bit_range(descriptors, 7, 1))
		add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown descriptor type.");

	if (size > 2)
		metadata->base.desired_content_max_luminance = parse_max_luminance(data[2]);
	if (size > 3)
		metadata->base.desired_content_max_frame_avg_luminance = parse_max_luminance(data[3]);
	if (size > 4) {
		if (metadata->base.desired_content_max_luminance == 0)
			add_failure(cta, "HDR Static Metadata Data Block: Desired content min luminance is set, but max luminance is unset.");
		else
			metadata->base.desired_content_min_luminance =
				parse_min_luminance(data[4], metadata->base.desired_content_max_luminance);
	}

	return 0;
}

static int
parse_hdr_dynamic_metadata_block(struct di_cta *cta,
				 struct di_cta_hdr_dynamic_metadata_block_priv *priv,
				 const uint8_t *data, size_t size)
{
	struct di_cta_hdr_dynamic_metadata_block *base;
	struct di_cta_hdr_dynamic_metadata_type1 *type1;
	struct di_cta_hdr_dynamic_metadata_type2 *type2;
	struct di_cta_hdr_dynamic_metadata_type3 *type3;
	struct di_cta_hdr_dynamic_metadata_type4 *type4;
	struct di_cta_hdr_dynamic_metadata_type256 *type256;
	size_t length;
	int type;

	base = &priv->base;
	type1 = &priv->type1;
	type2 = &priv->type2;
	type3 = &priv->type3;
	type4 = &priv->type4;
	type256 = &priv->type256;

	if (size < 3) {
		add_failure(cta, "HDR Dynamic Metadata Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	while (size >= 3) {
		length = data[0];

		if (size < length + 1) {
			add_failure(cta, "HDR Dynamic Metadata Data Block: Length of type bigger than block size.");
			return -EINVAL;
		}

		if (length < 2) {
			add_failure(cta, "HDR Dynamic Metadata Data Block: Type has wrong length.");
			return -EINVAL;
		}

		type = (data[2] << 8) | data[1];
		switch (type) {
		case 0x0001:
			if (length < 3) {
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 missing Support Flags.");
				break;
			}
			if (length != 3)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 length must be 3.");
			type1->type_1_hdr_metadata_version = get_bit_range(data[3], 3, 0);
			base->type1 = type1;
			if (get_bit_range(data[3], 7, 4) != 0)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bits 7-4 must be 0.");
			break;
		case 0x0002:
			if (length < 3) {
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 missing Support Flags.");
				break;
			}
			if (length != 3)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 length must be 3.");
			type2->ts_103_433_spec_version = get_bit_range(data[3], 3, 0);
			if (type2->ts_103_433_spec_version == 0) {
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 spec version of 0 is not allowed.");
				break;
			}
			type2->ts_103_433_1_capable = has_bit(data[3], 4);
			type2->ts_103_433_2_capable = has_bit(data[3], 5);
			type2->ts_103_433_3_capable = has_bit(data[3], 6);
			base->type2 = type2;
			if (has_bit(data[3], 7) != 0)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bit 7 must be 0.");
			break;
		case 0x0003:
			if (length != 2)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 3 length must be 2.");
			base->type3 = type3;
			break;
		case 0x0004:
			if (length < 3) {
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 missing Support Flags.");
				break;
			}
			if (length != 3)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 length must be 3.");
			type4->type_4_hdr_metadata_version = get_bit_range(data[3], 3, 0);
			base->type4 = type4;
			if (get_bit_range(data[3], 7, 4) != 0)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 support flags bits 7-4 must be 0.");
			break;
		case 0x0100:
			if (length < 3) {
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 missing Support Flags.");
				break;
			}
			if (length != 3)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 length must be 3.");
			type256->graphics_overlay_flag_version = get_bit_range(data[3], 3, 0);
			base->type256 = type256;
			if (get_bit_range(data[3], 7, 4) != 0)
				add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 support flags bits 7-4 must be 0.");
			break;
		default:
			add_failure(cta, "HDR Dynamic Metadata Data Block: Unknown Type 0x%04x.", type);
			break;
		}

		size -= length + 1;
		data += length + 1;
	}

	return 0;
}

static int
parse_vesa_transfer_characteristics_block(struct di_cta *cta,
					  struct di_cta_vesa_transfer_characteristics_block *tf,
					  const uint8_t *data, size_t size)
{
	size_t i;

	if (size != 7 && size != 15 && size != 31) {
		add_failure(cta, "Invalid length %u.", size);
		return -EINVAL;
	}

	tf->points_len = (uint8_t) size + 1;
	tf->usage = get_bit_range(data[0], 7, 6);

	tf->points[0] = get_bit_range(data[0], 5, 0) / 1023.0f;
	for (i = 1; i < size; i++)
		tf->points[i] = tf->points[i - 1] + data[i] / 1023.0f;
	tf->points[i] = 1.0f;

	return 0;
}

static int
parse_video_format_pref_block(struct di_cta *cta,
			      struct di_cta_video_format_pref_priv *vfpdb,
			      const uint8_t *data, size_t size)
{
	struct di_cta_svr *svr;
	size_t i;
	uint8_t code;

	for (i = 0; i < size; i++) {
		code = data[i];

		if (code == 0 ||
		    code == 128 ||
		    (code >= 161 && code <= 192) ||
		    code == 255) {
			add_failure(cta, "Video Format Preference Data Block: "
				    "using reserved Short Video Reference value %u.",
				    code);
			continue;
		}

		svr = calloc(1, sizeof(*svr));
		if (!svr)
			return -errno;

		if ((code >= 1 && code <= 127) ||
		    (code >= 193 && code <= 253)) {
			svr->type = DI_CTA_SVR_TYPE_VIC;
			svr->vic.code = code;
		} else if (code >= 129 && code <= 144) {
			svr->type = DI_CTA_SVR_TYPE_DTD_INDEX;
			svr->dtd_index = code - 129;
		} else if (code >= 145 && code <= 160) {
			svr->type = DI_CTA_SVR_TYPE_T7T10VTDB;
			svr->dtd_index = code - 145;
		} else if (code == 254) {
			svr->type = DI_CTA_SVR_TYPE_FIRST_T8VTDB;
		} else {
			abort(); /* unreachable */
		}

		assert(vfpdb->svrs_len < EDID_CTA_MAX_VIDEO_FORMAT_PREF_BLOCK_ENTRIES);
		vfpdb->svrs[vfpdb->svrs_len++] = svr;
	}

	vfpdb->base.svrs = (const struct di_cta_svr *const *)vfpdb->svrs;
	return 0;
}


static void
parse_ycbcr420_cap_map(struct di_cta *cta,
		       struct di_cta_ycbcr420_cap_map_block *ycbcr420_cap_map,
		       const uint8_t *data, size_t size)
{
	if (size == 0) {
		ycbcr420_cap_map->all = true;
		return;
	}

	assert(size <= sizeof(ycbcr420_cap_map->svd_bitmap));
	memcpy(ycbcr420_cap_map->svd_bitmap, data, size);
}

static bool
parse_hdmi_audio_3d_descriptor(struct di_cta *cta,
			       struct di_cta_sad_priv *sad,
			       const uint8_t *data, size_t size)
{
	/* Contains the same data as the Short Audio Descriptor, packed differently */
	struct di_cta_sad *base = &sad->base;
	struct di_cta_sad_sample_rates *sample_rate = &sad->supported_sample_rates;
	struct di_cta_sad_lpcm *lpcm = &sad->lpcm;
	uint8_t code;

	assert(size >= CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE);

	code = get_bit_range(data[0], 3, 0);
	if (!parse_sad_format(cta, code, 0, &base->format, "HDMI Audio Data Block"))
		return false;

	if (base->format != DI_CTA_AUDIO_FORMAT_LPCM &&
	    base->format != DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO) {
		add_failure(cta,
			    "HDMI Audio Data Block: Unsupported 3D Audio Format 0x%04x.",
			    code);
		return false;
	}

	base->max_channels = get_bit_range(data[1], 4, 0) + 1;
	sample_rate->has_192_khz = has_bit(data[2], 6);
	sample_rate->has_176_4_khz = has_bit(data[2], 5);
	sample_rate->has_96_khz = has_bit(data[2], 4);
	sample_rate->has_88_2_khz = has_bit(data[2], 3);
	sample_rate->has_48_khz = has_bit(data[2], 2);
	sample_rate->has_44_1_khz = has_bit(data[2], 1);
	sample_rate->has_32_khz = has_bit(data[2], 0);
	base->supported_sample_rates = sample_rate;

	if (base->format == DI_CTA_AUDIO_FORMAT_LPCM) {
		lpcm->has_sample_size_24_bits = has_bit(data[3], 2);
		lpcm->has_sample_size_20_bits = has_bit(data[3], 1);
		lpcm->has_sample_size_16_bits = has_bit(data[3], 0);
		base->lpcm = lpcm;
	}

	if (base->format == DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO) {
		/* TODO data[3] 7:0 contains unknown Audio Format Code dependent value */
	}

	return true;
}

static int
parse_hdmi_audio_block(struct di_cta *cta,
		       struct di_cta_hdmi_audio_block_priv *priv,
		       const uint8_t *data, size_t size)
{
	struct di_cta_hdmi_audio_block *hdmi_audio = &priv->base;
	struct di_cta_hdmi_audio_multi_stream *ms = &priv->ms;
	struct di_cta_hdmi_audio_3d *a3d = &priv->a3d;
	uint8_t multi_stream;
	bool ms_non_mixed;
	size_t num_3d_audio_descs;
	size_t num_descs;
	struct di_cta_sad_priv *sad_priv;
	uint8_t channels;

	if (size < 1) {
		add_failure(cta, "HDMI Audio Data Block: Empty Data Block with length 0.");
		return -EINVAL;
	}

	multi_stream = get_bit_range(data[0], 1, 0);
	ms_non_mixed = has_bit(data[0], 2);

	if (multi_stream > 0) {
		hdmi_audio->multi_stream = ms;
		ms->max_streams = multi_stream + 1;
		ms->supports_non_mixed = ms_non_mixed;
	} else if (ms_non_mixed) {
		add_failure(cta, "HDMI Audio Data Block: MS NonMixed support indicated but "
				 "Max Stream Count == 0.");
	}

	if (size < 2)
		return 0;

	num_3d_audio_descs = get_bit_range(data[1], 2, 0);
	if (num_3d_audio_descs == 0)
		return 0;

	/* If there are 3d Audio Descriptors, there is one last Speaker Allocation Descriptor */
	num_descs = num_3d_audio_descs + 1;

	/* Skip to the first descriptor */
	size -= 2;
	data += 2;

	/* Make sure there is enough space for the descriptors */
	if (num_descs > size / CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE) {
		add_failure(cta, "HDMI Audio Data Block: More descriptors indicated than block size allows.");
		return 0;
	}

	hdmi_audio->audio_3d = a3d;
	a3d->sads = (const struct di_cta_sad * const*)priv->sads;

	/* First the 3D Audio Descriptors, the last one is the 3D Speaker Allocation Descriptor */
	while (num_descs > 1) {
		sad_priv = calloc(1, sizeof(*sad_priv));
		if (!sad_priv)
			return -errno;

		if (!parse_hdmi_audio_3d_descriptor(cta, sad_priv, data, size)) {
			free(sad_priv);
			goto skip;
		}

		assert(priv->sads_len < EDID_CTA_MAX_HDMI_AUDIO_BLOCK_ENTRIES);
		priv->sads[priv->sads_len++] = sad_priv;

skip:
		num_descs--;
		size -= CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE;
		data += CTA_HDMI_AUDIO_3D_DESCRIPTOR_SIZE;
	}

	channels = get_bit_range(data[3], 7, 4);

	switch (channels) {
	case DI_CTA_HDMI_AUDIO_3D_CHANNELS_UNKNOWN:
	case DI_CTA_HDMI_AUDIO_3D_CHANNELS_10_2:
	case DI_CTA_HDMI_AUDIO_3D_CHANNELS_22_2:
	case DI_CTA_HDMI_AUDIO_3D_CHANNELS_30_2:
		a3d->channels = channels;
		break;
	default:
		a3d->channels = DI_CTA_HDMI_AUDIO_3D_CHANNELS_UNKNOWN;
		break;
	}

	parse_speaker_alloc(cta, &a3d->speakers, data, "Room Configuration Data Block");

	return 0;
}

static struct di_cta_infoframe_descriptor *
parse_infoframe(struct di_cta *cta, uint8_t type,
		const uint8_t *data, size_t size)
{
	struct di_cta_infoframe_descriptor infoframe = {0};
	struct di_cta_infoframe_descriptor *ifp;

	if (type >= 8 && type <= 0x1f) {
		add_failure(cta, "InfoFrame Data Block: Type code %u is reserved.",
			    type);
		return NULL;
	}

	if (type >= 0x20) {
		add_failure(cta, "InfoFrame Data Block: Type code %u is forbidden.",
			    type);
		return NULL;
	}

	if (type == 1) {
		/* No known vendor specific InfoFrames, yet */
		return NULL;
	} else {
		switch (type) {
		case 0x02:
			infoframe.type = DI_CTA_INFOFRAME_TYPE_AUXILIARY_VIDEO_INFORMATION;
			break;
		case 0x03:
			infoframe.type = DI_CTA_INFOFRAME_TYPE_SOURCE_PRODUCT_DESCRIPTION;
			break;
		case 0x04:
			infoframe.type = DI_CTA_INFOFRAME_TYPE_AUDIO;
			break;
		case 0x05:
			infoframe.type = DI_CTA_INFOFRAME_TYPE_MPEG_SOURCE;
			break;
		case 0x06:
			infoframe.type = DI_CTA_INFOFRAME_TYPE_NTSC_VBI;
			break;
		case 0x07:
			infoframe.type = DI_CTA_INFOFRAME_TYPE_DYNAMIC_RANGE_AND_MASTERING;
			break;
		default:
			abort(); /* unreachable */
		}
	}

	ifp = calloc(1, sizeof(*ifp));
	if (!ifp)
		return NULL;

	*ifp = infoframe;
	return ifp;
}

static int
parse_infoframe_block(struct di_cta *cta,
		      struct di_cta_infoframe_block_priv *ifb,
		      const uint8_t *data, size_t size)
{
	size_t index = 0, length;
	uint8_t type;
	struct di_cta_infoframe_descriptor *infoframe;

	if (size < 2) {
		add_failure(cta, "InfoFrame Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	ifb->base.num_simultaneous_vsifs = data[1] + 1;
	ifb->base.infoframes = (const struct di_cta_infoframe_descriptor *const *)ifb->infoframes;

	index = get_bit_range(data[0], 7, 5) + 2;
	if (get_bit_range(data[0], 4, 0) != 0)
		add_failure(cta, "InfoFrame Data Block: InfoFrame Processing "
				 "Descriptor Header bits F14-F10 shall be 0.");

	while (true) {
		if (index == size)
			break;
		if (index > size) {
			add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size.");
			return -EINVAL;
		}

		length = get_bit_range(data[index], 7, 5);
		type = get_bit_range(data[index], 4, 0);

		if (type == 0) {
			add_failure(cta, "InfoFrame Data Block: Short InfoFrame Descriptor with type 0 is forbidden.");
			return -EINVAL;
		} else if (type == 1) {
			length += 4;
		} else {
			length += 1;
		}

		if (index + length > size) {
			add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size.");
			return -EINVAL;
		}

		infoframe = parse_infoframe(cta, type, &data[index], length);
		if (infoframe) {
			assert(ifb->infoframes_len < EDID_CTA_INFOFRAME_BLOCK_ENTRIES);
			ifb->infoframes[ifb->infoframes_len++] = infoframe;
		}

		index += length;
	}

	return 0;
}

static double
decode_coord(unsigned char x)
{
	signed char s = (signed char)x;

	return s / 64.0;
}

static int
parse_room_config_block(struct di_cta *cta,
			struct di_cta_room_configuration_block *rc,
			const uint8_t *data, size_t size)
{
	bool has_display_coords;
	bool has_speaker_count;

	if (size < 4) {
		add_failure(cta, "Room Configuration Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	has_display_coords = has_bit(data[0], 7);
	has_speaker_count = has_bit(data[0], 6);
	rc->has_speaker_location_descriptors = has_bit(data[0], 5);

	if (has_speaker_count) {
		rc->speaker_count = get_bit_range(data[0], 4, 0) + 1;
	} else {
		if (get_bit_range(data[0], 4, 0) != 0) {
			add_failure(cta, "Room Configuration Data Block: "
					 "'Speaker' flag is 0, but the Speaker Count is not 0.");
		}

		if (rc->has_speaker_location_descriptors) {
			add_failure(cta, "Room Configuration Data Block: "
					 "'Speaker' flag is 0, but there are "
					 "Speaker Location Descriptors.");
		}
	}

	parse_speaker_alloc(cta, &rc->speakers, &data[1], "Room Configuration Data Block");

	rc->max_x = 16;
	rc->max_y = 16;
	rc->max_z = 8;
	rc->display_x = 0.0;
	rc->display_y = 1.0;
	rc->display_z = 0.0;

	if (size < 7) {
		if (has_display_coords)
			add_failure(cta, "Room Configuration Data Block: "
					 "'Display' flag is 1, but the Display and Maximum coordinates are not present.");
		return 0;
	}

	rc->max_x = data[4];
	rc->max_y = data[5];
	rc->max_z = data[6];

	if (size < 10) {
		if (has_display_coords)
			add_failure(cta, "Room Configuration Data Block: "
					 "'Display' flag is 1, but the Display coordinates are not present.");
		return 0;
	}

	rc->display_x = decode_coord(data[7]);
	rc->display_y = decode_coord(data[8]);
	rc->display_z = decode_coord(data[9]);

	return 0;
}

static int
parse_speaker_location_block(struct di_cta *cta,
			     struct di_cta_speaker_location_priv *sldb,
			     const uint8_t *data, size_t size)
{
	struct di_cta_speaker_location_descriptor speaker_loc, *slp;

	if (size < 2) {
		add_failure(cta, "Speaker Location Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	while (size >= 2) {
		speaker_loc.has_coords = has_bit(data[0], 6);
		speaker_loc.x = speaker_loc.y = speaker_loc.z = 0;
		speaker_loc.is_active = has_bit(data[0], 5);
		speaker_loc.channel_index = get_bit_range(data[0], 4, 0);
		speaker_loc.speaker_id = get_bit_range(data[1], 4, 0);

		if (has_bit(data[0], 7) || get_bit_range(data[1], 7, 5) != 0) {
			add_failure(cta, "Speaker Location Data Block: Bits F27-F25, F17 must be 0.");
		}

		if (speaker_loc.has_coords && size >= 5) {
			speaker_loc.x = decode_coord(data[2]);
			speaker_loc.y = decode_coord(data[3]);
			speaker_loc.z = decode_coord(data[4]);
			size -= 5;
			data += 5;
		} else if (speaker_loc.has_coords) {
			add_failure(cta, "Speaker Location Data Block: COORD bit "
					 "set but contains no Coordinates.");
			return -EINVAL;
		} else {
			size -= 2;
			data += 2;
		}

		slp = calloc(1, sizeof(*slp));
		if (!slp)
			return -errno;

		*slp = speaker_loc;
		assert(sldb->locations_len < EDID_CTA_MAX_SPEAKER_LOCATION_BLOCK_ENTRIES);
		sldb->locations[sldb->locations_len++] = slp;
	}

	sldb->base.locations =
		(const struct di_cta_speaker_location_descriptor *const *)sldb->locations;
	return 0;
}

static int
parse_did_type_vii_timing(struct di_cta *cta,
			  struct di_cta_type_vii_timing_priv *t,
			  const uint8_t *data, size_t size)
{
	uint8_t revision;

	if (size != 21) {
		add_failure(cta, "DisplayID Type VII Video Timing Data Block: "
				 "Empty Data Block with length %u.", size);
		return -EINVAL;
	}

	if (get_bit_range(data[0], 6, 4) != 0) {
		add_failure(cta, "DisplayID Type VII Video Timing Data Block: "
				 "T7_M shall be 000b.");
		return -EINVAL;
	}

	revision = get_bit_range(data[0], 2, 0);
	if (revision != 2) {
		add_failure(cta, "DisplayID Type VII Video Timing Data Block: "
				 "Unexpected revision (%u != %u).",
			    revision, 2);
		return -EINVAL;
	}

	if (has_bit(data[0], 3)) {
		add_failure(cta, "DisplayID Type VII Video Timing Data Block: "
				 "DSC_PT shall be 0.");
	}
	if (has_bit(data[0], 7)) {
		add_failure(cta, "DisplayID Type VII Video Timing Data Block: "
				 "Block Revision and Other Data Bit 7 must be 0.");
	}

	data += 1;
	size -= 1;

	_di_displayid_parse_type_1_7_timing(&t->timing, cta->logger,
					    "DisplayID Type VII Video Timing Data Block",
					    data, true);

	t->base.timing = &t->timing;
	return 0;
}

static int
peak_lum_index_to_nits(int index)
{
	switch (index) {
	case 1:
		return 200;
	case 2:
		return 300;
	case 3:
		return 400;
	case 4:
		return 500;
	case 5:
		return 600;
	case 6:
		return 800;
	case 7:
		return 1000;
	case 8:
		return 1200;
	case 9:
		return 1500;
	case 10:
		return 2000;
	case 11:
		return 2500;
	case 12:
		return 3000;
	case 13:
		return 4000;
	case 14:
		return 6000;
	case 15:
		return 8000;
	}

	return 0;
}

static int
ff_peak_lum_index_to_nits(int index, int peak_lum)
{
	float mult;

	/**
	 * Full Frame Peak Luminance index maps to a certain percentage of the
	 * Peak Luminance.
	 */

	if (peak_lum == 0)
		return 0;

	switch (index) {
	case 0:
		mult = 0.1f;
		break;
	case 1:
		mult = 0.2f;
		break;
	case 2:
		mult = 0.4f;
		break;
	case 3:
		mult = 0.8f;
		break;
	default:
		mult = 0.0f;
		break;
	}

	return (int)roundf((float)peak_lum * mult);
}

static int
parse_hdr10plus_block(struct di_cta *cta,
		      struct di_cta_hdr10plus_block *block,
		      const uint8_t *data, size_t size)
{
	int peak_lum_index, ff_peak_lum_index;

	if (size < 1) {
		add_failure(cta, "Vendor-Specific Video Data Block (HDR10+), OUI 90-84-8B: "
				 "Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	block->version = get_bit_range(data[0], 1, 0);
	if (block->version != 1) {
		add_failure(cta, "Vendor-Specific Video Data Block (HDR10+), OUI 90-84-8B: "
				 "We were expecting application version 1, but got %d.",
			    block->version);
		return -EINVAL;
	}

	/* Index 0 is reserved and > 15 invalid (but 4 bits goes up to 15). */
	peak_lum_index = get_bit_range(data[0], 7, 4);
	if (peak_lum_index == 0) {
		add_failure(cta, "Vendor-Specific Video Data Block (HDR10+), OUI 90-84-8B: "
				 "Peak luminance index 0 is reserved.");
	}
	block->peak_lum = peak_lum_index_to_nits(peak_lum_index);

	/* Index > 3 is invalid, but 2 bits goes up to 3 so no need to check. */
	ff_peak_lum_index = get_bit_range(data[0], 3, 2);
	block->ff_peak_lum = ff_peak_lum_index_to_nits(ff_peak_lum_index,
						       block->peak_lum);

	return 0;
}

static int
parse_dolby_video_block(struct di_cta *cta,
			 struct di_cta_dolby_video_block_priv *dv_priv,
			 const uint8_t *data, size_t size)
{
	struct di_cta_dolby_video_block *dv = &dv_priv->base;
	struct di_cta_dolby_video_block_v0 *v0 = &dv_priv->v0;
	struct di_cta_dolby_video_block_v1 *v1 = &dv_priv->v1;
	struct di_cta_dolby_video_block_v2 *v2 = &dv_priv->v2;
	int version;

	if (size < 1) {
		add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: "
				 "Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}
	version = get_bit_range(data[0], 7, 5);

	if (version == 0) {
		dv->version = DI_CTA_DOLBY_VIDEO_VERSION0;
		dv->v0 = v0;

		if (size < 17) {
			add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: "
					 "Expected length of 17 for Version 0, but got length %u.",
				    size);
			return -EINVAL;
		}

		v0->global_dimming = has_bit(data[0], 2);
		v0->supports_2160p60 = has_bit(data[0], 1);
		v0->yuv422_12bit = has_bit(data[0], 0);

		/* TODO unused: get_bit_range(data[0], 4, 3) */

		v0->dynamic_metadata_version_major = get_bit_range(data[16], 7, 4);
		v0->dynamic_metadata_version_minor = get_bit_range(data[16], 3, 0);

		v0->target_pq_12b_level_min = data[14] << 4 | get_bit_range(data[13], 7, 4);
		v0->target_pq_12b_level_max = data[15] << 4 | get_bit_range(data[13], 3, 0);

		v0->red_x = ((data[2] << 4) | get_bit_range(data[1], 7, 4)) / 4096.0;
		v0->red_y = ((data[3] << 4) | get_bit_range(data[1], 3, 0)) / 4096.0;
		v0->green_x = ((data[5] << 4) | get_bit_range(data[4], 7, 4)) / 4096.0;
		v0->green_y = ((data[6] << 4) | get_bit_range(data[4], 3, 0)) / 4096.0;
		v0->blue_x = ((data[8] << 4) | get_bit_range(data[7], 7, 4)) / 4096.0;
		v0->blue_y = ((data[9] << 4) | get_bit_range(data[7], 3, 0)) / 4096.0;
		v0->white_x = ((data[11] << 4) | get_bit_range(data[10], 7, 4)) / 4096.0;
		v0->white_y = ((data[12] << 4) | get_bit_range(data[10], 3, 0)) / 4096.0;
	} else if (version == 1) {
		double lm;
		double xmin, ymin;
		double xstep, ystep;
		int steps;

		dv->version = DI_CTA_DOLBY_VIDEO_VERSION1;
		dv->v1 = v1;

		if (size < 7) {
			add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: "
					 "Expected length of at least 7 for Version 1, "
					 "but got length %u.",
				    size);
			return -EINVAL;
		}

		v1->dynamic_metadata_version = get_bit_range(data[0], 4, 2) + 2;
		v1->supports_2160p60 = has_bit(data[0], 1);
		v1->yuv422_12bit = has_bit(data[0], 0);
		v1->global_dimming = has_bit(data[1], 0);

		v1->colorimetry = has_bit(data[2], 0) ?
					DI_CTA_DOLBY_VIDEO_COLORIMETRY_P3_D65 :
					DI_CTA_DOLBY_VIDEO_COLORIMETRY_BT_709;

		if (has_bit(data[3], 0))
			v1->mode_low_latency = true;

		lm = get_bit_range(data[2], 7, 1) / 127.0;
		v1->target_luminance_min = lm * lm;

		lm = (get_bit_range(data[1], 7, 1) * 50.0) + 100;
		v1->target_luminance_max = lm;

		if (size >= 10) {
			v1->unique_primaries = false;
			v1->red_x = data[4] / 256.0;
			v1->red_y = data[5] / 256.0;
			v1->green_x = data[6] / 256.0;
			v1->green_y = data[7] / 256.0;
			v1->blue_x = data[8] / 256.0;
			v1->blue_y = data[9] / 256.0;

			/* TODO unused: get_bit_range(data[3], 7, 1) */
		} else {
			v1->unique_primaries = true;
			xmin = 0.625;
			xstep = (0.74609375 - xmin) / 31.0;
			v1->red_x = xmin + xstep * (data[6] >> 3);

			ymin = 0.25;
			ystep = (0.37109375 - ymin) / 31.0;
			steps = (get_bit_range(data[6], 2, 0) << 2) |
				(get_bit_range(data[5], 0, 0) << 1) |
				get_bit_range(data[4], 0, 0);
			v1->red_y = ymin + ystep * steps;

			xstep = 0.49609375 / 127.0;
			v1->green_x = xstep * get_bit_range(data[4], 7, 1);

			ymin = 0.5;
			ystep = (0.99609375 - ymin) / 127.0;
			v1->green_y = ymin + ystep * get_bit_range(data[5], 7, 1);

			xmin = 0.125;
			xstep = (0.15234375 - xmin) / 7.0;
			v1->blue_x = xmin + xstep * get_bit_range(data[3], 7, 5);

			ymin = 0.03125;
			ystep = (0.05859375 - ymin) / 7.0;
			v1->blue_y = ymin + ystep * get_bit_range(data[3], 4, 2);

			/* TODO unused: get_bit_range(data[3], 1, 1) */
		}
	} else if (version == 2) {
		dv->version = DI_CTA_DOLBY_VIDEO_VERSION2;
		dv->v2 = v2;

		if (size < 7) {
			add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: "
					 "Expected length of at least 7 for Version 2, "
					 "but got length %u.",
				    size);
			return -EINVAL;
		}

		v2->dynamic_metadata_version = get_bit_range(data[0], 4, 2) + 2;
		v2->backlight_control = has_bit(data[0], 1);
		v2->yuv422_12bit = has_bit(data[0], 0);
		v2->global_dimming = has_bit(data[1], 2);

		v2->backlight_luminance_min = 25 + get_bit_range(data[1], 1, 0) * 25;

		switch (get_bit_range(data[2], 1, 0)) {
		case 0:
			break;
		case 1:
			v2->mode_low_latency_hdmi = true;
			break;
		case 2:
			v2->mode_standard = true;
			break;
		case 3:
			v2->mode_standard = true;
			v2->mode_low_latency_hdmi = true;
			break;
		default:
			abort(); /* unreachable */
		}

		v2->yuv444 = (uint8_t)(get_bit_range(data[3], 0, 0) << 1) |
			     get_bit_range(data[4], 0, 0);
		switch (v2->yuv444) {
		case DI_CTA_DOLBY_VIDEO_YUV444_NONE:
		case DI_CTA_DOLBY_VIDEO_YUV444_10_BITS:
		case DI_CTA_DOLBY_VIDEO_YUV444_12_BITS:
			break;
		default:
			add_failure(cta, "Vendor-Specific Video Data Block (Dolby), OUI 00-D0-46: "
					 "Reserved YUV444 mode 0x%02x.",
				    v2->yuv444);
			return -EINVAL;
		}

		v2->target_pq_12b_level_min = 20 * get_bit_range(data[1], 7, 3);
		v2->target_pq_12b_level_max = 2055 + 65 * get_bit_range(data[2], 7, 3);

		v2->red_x = 0.625 + get_bit_range(data[5], 7, 3) / 256.0;
		v2->red_y = 0.25 + get_bit_range(data[6], 7, 3) / 256.0;

		v2->green_x = get_bit_range(data[3], 7, 1) / 256.0;
		v2->green_y = 0.5 + get_bit_range(data[4], 7, 1) / 256.0;

		v2->blue_x = 0.125 + get_bit_range(data[5], 2, 0) / 256.0;
		v2->blue_y = 0.03125 + get_bit_range(data[6], 2, 0) / 256.0;
	}

	return 0;
}

void
_di_cta_data_block_destroy(struct di_cta_data_block *data_block)
{
	size_t i;
	struct di_cta_video_block_priv *video;
	struct di_cta_audio_block_priv *audio;
	struct di_cta_infoframe_block_priv *infoframe;
	struct di_cta_speaker_location_priv *speaker_location;
	struct di_cta_video_format_pref_priv *vfpdb;
	struct di_cta_hdmi_audio_block_priv *hdmi_audio;
	struct di_cta_vendor_hdmi_block_priv *vendor_hdmi;
	struct di_cta_ycbcr420_video_block_priv *ycbcr420;

	switch (data_block->tag) {
	case DI_CTA_DATA_BLOCK_VIDEO:
		video = &data_block->video;
		for (i = 0; i < video->svds_len; i++)
			free(video->svds[i]);
		break;
	case DI_CTA_DATA_BLOCK_VENDOR_HDMI:
		vendor_hdmi = &data_block->vendor_hdmi;
		free(vendor_hdmi->vics);
		break;
	case DI_CTA_DATA_BLOCK_YCBCR420:
		ycbcr420 = &data_block->ycbcr420;
		for (i = 0; i < ycbcr420->svds_len; i++)
			free(ycbcr420->svds[i]);
		break;
	case DI_CTA_DATA_BLOCK_AUDIO:
		audio = &data_block->audio;
		for (i = 0; i < audio->sads_len; i++)
			free(audio->sads[i]);
		break;
	case DI_CTA_DATA_BLOCK_INFOFRAME:
		infoframe = &data_block->infoframe;
		for (i = 0; i < infoframe->infoframes_len; i++)
			free(infoframe->infoframes[i]);
		break;
	case DI_CTA_DATA_BLOCK_SPEAKER_LOCATION:
		speaker_location = &data_block->speaker_location;
		for (i = 0; i < speaker_location->locations_len; i++)
			free(speaker_location->locations[i]);
		break;
	case DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF:
		vfpdb = &data_block->video_format_pref;
		for (i = 0; i < vfpdb->svrs_len; i++)
			free(vfpdb->svrs[i]);
		break;
	case DI_CTA_DATA_BLOCK_HDMI_AUDIO:
		hdmi_audio = &data_block->hdmi_audio;
		for (i = 0; i < hdmi_audio->sads_len; i++)
			free(hdmi_audio->sads[i]);
		break;
	default:
		/* Nothing to do. Blocks that we failed to fully initialize may
		 * not contain a tag (zeroed), and these should only need the
		 * base block destruction. And some types of data blocks simply
		 * don't have specific data to be released. */
		break;
	}

	free(data_block);
}

static int
parse_vendor_specific_video_block(struct di_cta *cta,
				  enum di_cta_data_block_tag *tag,
				  struct di_cta_data_block *data_block,
				  const uint8_t *data, size_t size)
{
	uint32_t oui;

	if (size < 3) {
		add_failure(cta,
			    "Vendor-Specific Video Data Block: Empty Data Block with length %u.",
			    size);
		return -EINVAL;
	}

	oui = ((uint32_t)data[2] << 16) | ((uint32_t)data[1] << 8) | data[0];

	data += 3;
	size -= 3;

	switch (oui) {
	case IEEE_OUI_DOLBY:
		*tag = DI_CTA_DATA_BLOCK_DOLBY_VIDEO;
		return parse_dolby_video_block(cta, &data_block->dolby_video,
					       data, size);
	case IEEE_OUI_HDR10PLUS:
		*tag = DI_CTA_DATA_BLOCK_HDR10PLUS;
		return parse_hdr10plus_block(cta, &data_block->hdr10plus,
					     data, size);
	default:
		return -EINVAL;
	}
}

static int
parse_vendor_specific_block(struct di_cta *cta,
			    enum di_cta_data_block_tag *tag,
			    struct di_cta_data_block *data_block,
			    const uint8_t *data, size_t size)
{
	uint32_t oui;

	if (size < 3) {
		add_failure(cta,
			    "Vendor-Specific Data Block: Empty Data Block with length (%u).",
			    size);
		return -EINVAL;
	}

	oui = ((uint32_t)data[2] << 16) | ((uint32_t)data[1] << 8) | data[0];

	switch (oui) {
	case IEEE_OUI_HDMI:
		*tag = DI_CTA_DATA_BLOCK_VENDOR_HDMI;
		return parse_vendor_hdmi_block(cta, &data_block->vendor_hdmi,
					       data, size);
	case IEEE_OUI_HDMI_FORUM:
		*tag = DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM;
		return parse_vendor_hdmi_forum_block(cta, &data_block->vendor_hdmi_forum,
						     data, size);
	default:
		return -EINVAL;
	}
}

bool
_di_cta_data_block_parse(struct di_cta *cta, uint8_t raw_tag, const uint8_t *data, size_t size,
			 struct di_cta_data_block **data_block_out)
{
	uint8_t extended_tag;
	struct di_cta_data_block *data_block;
	int ret;

	*data_block_out = NULL;

	data_block = calloc(1, sizeof(*data_block));
	if (!data_block) {
		return false;
	}

	ret = 0;
	switch (raw_tag) {
	case 1:
		data_block->tag = DI_CTA_DATA_BLOCK_AUDIO;
		ret = parse_audio_block(cta, &data_block->audio, data, size);
		break;
	case 2:
		data_block->tag = DI_CTA_DATA_BLOCK_VIDEO;
		ret = parse_video_block(cta, &data_block->video, data, size);
		break;
	case 3: /* Vendor-Specific Data Block */
		ret = parse_vendor_specific_block(cta, &data_block->tag, data_block, data, size);
		break;
	case 4:
		data_block->tag = DI_CTA_DATA_BLOCK_SPEAKER_ALLOC;
		ret = parse_speaker_alloc_block(cta, &data_block->speaker_alloc,
					        data, size);
		break;
	case 5:
		data_block->tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC;
		ret = parse_vesa_transfer_characteristics_block(cta,
							        &data_block->vesa_transfer_characteristics,
							        data, size);
		break;
	case 6:
		data_block->tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT;
		break;
	case 7:
		/* Use Extended Tag */
		if (size < 1) {
			add_failure(cta, "Empty block with extended tag.");
			ret = -EINVAL;
			break;
		}

		extended_tag = data[0];
		data = &data[1];
		size--;

		switch (extended_tag) {
		case 0:
			data_block->tag = DI_CTA_DATA_BLOCK_VIDEO_CAP;
			ret = parse_video_cap_block(cta, &data_block->video_cap,
						   data, size);
			break;
		case 2:
			data_block->tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE;
			ret = parse_vesa_display_device(cta, &data_block->vesa_display_device,
							data, size);
			break;
		case 5:
			data_block->tag = DI_CTA_DATA_BLOCK_COLORIMETRY;
			ret = parse_colorimetry_block(cta,
						      &data_block->colorimetry,
						      data, size);
			break;
		case 6:
			data_block->tag = DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA;
			ret = parse_hdr_static_metadata_block(cta,
							      &data_block->hdr_static_metadata,
							      data, size);
			break;
		case 7:
			data_block->tag = DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA;
			ret = parse_hdr_dynamic_metadata_block(cta,
							       &data_block->hdr_dynamic_metadata,
							       data, size);
			break;
		case 8:
			data_block->tag = DI_CTA_DATA_BLOCK_NATIVE_VIDEO_RESOLUTION;
			break;
		case 13:
			data_block->tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF;
			ret = parse_video_format_pref_block(cta,
							    &data_block->video_format_pref,
							    data, size);
			break;
		case 14:
			data_block->tag = DI_CTA_DATA_BLOCK_YCBCR420;
			ret = parse_ycbcr420_block(cta,
						   &data_block->ycbcr420,
						   data, size);
			break;
		case 15:
			data_block->tag = DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP;
			parse_ycbcr420_cap_map(cta,
					       &data_block->ycbcr420_cap_map,
					       data, size);
			break;
		case 18:
			data_block->tag = DI_CTA_DATA_BLOCK_HDMI_AUDIO;
			ret = parse_hdmi_audio_block(cta,
						     &data_block->hdmi_audio,
						     data, size);
			break;
		case 19:
			data_block->tag = DI_CTA_DATA_BLOCK_ROOM_CONFIG;
			ret = parse_room_config_block(cta,
						      &data_block->room_config,
						      data, size);
			break;
		case 20:
			data_block->tag = DI_CTA_DATA_BLOCK_SPEAKER_LOCATION;
			ret = parse_speaker_location_block(cta,
							   &data_block->speaker_location,
							   data, size);
			break;
		case 32:
			data_block->tag = DI_CTA_DATA_BLOCK_INFOFRAME;
			ret = parse_infoframe_block(cta,
						    &data_block->infoframe,
						    data, size);
			break;
		case 34:
			data_block->tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII;
			ret = parse_did_type_vii_timing(cta,
						        &data_block->did_vii_timing,
						        data, size);
			break;
		case 35:
			data_block->tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VIII;
			break;
		case 42:
			data_block->tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_X;
			break;
		case 120:
			data_block->tag = DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE;
			break;
		case 121:
			data_block->tag = DI_CTA_DATA_BLOCK_HDMI_SINK_CAP;
			/**
			 * This expects data to include the extended tag. Let's
			 * get back one byte.
			 */
			data -= sizeof(data[0]);
			size++;
			ret = parse_hdmi_forum_sink_cap(cta, &data_block->hdmi_sink_cap,
						        data, size);
			break;
		case 1: /* Vendor-Specific Video Data Block */
			ret = parse_vendor_specific_video_block(cta, &data_block->tag,
							        data_block,
							        data, size);
			break;
		case 17: /* Vendor-Specific Audio Data Block */
			ret = -EINVAL;
			break;
		default:
			/* Reserved */
			add_failure_until(cta, 3,
					  "Unknown CTA-861 Data Block (extended tag 0x"PRIx8", length %zu).",
					  extended_tag, size);
			ret = -EINVAL;
			break;
		}
		break;
	default:
		/* Reserved */
		add_failure_until(cta, 3, "Unknown CTA-861 Data Block (tag 0x"PRIx8", length %zu).",
				  raw_tag, size);
		ret = -EINVAL;
		break;
	}
	if (ret != 0) {
		_di_cta_data_block_destroy(data_block);
		return ret == -EINVAL || ret == -ENOTSUP;
	}

	*data_block_out = data_block;
	return true;
}

bool
_di_edid_cta_parse(struct di_edid_cta *edid_cta, const uint8_t *data, size_t size,
		   struct di_logger *logger)
{
	struct di_cta *cta;
	uint8_t flags, dtd_start;
	uint8_t data_block_header, data_block_tag, data_block_size;
	size_t i;
	struct di_edid_detailed_timing_def_priv *detailed_timing_def;
	struct di_cta_data_block *data_block;

	assert(size == 128);
	assert(data[0] == 0x02);

	edid_cta->revision = data[1];
	dtd_start = data[2];

	cta = &(struct di_cta){
		.revision = edid_cta->revision,
		.flags = &edid_cta->flags,
		.logger = logger,
	};

	flags = data[3];
	if (cta->revision >= 2) {
		edid_cta->flags.it_underscan = has_bit(flags, 7);
		edid_cta->flags.basic_audio = has_bit(flags, 6);
		edid_cta->flags.ycc444 = has_bit(flags, 5);
		edid_cta->flags.ycc422 = has_bit(flags, 4);
		edid_cta->flags.native_dtds = get_bit_range(flags, 3, 0);
	} else if (flags != 0) {
		/* Reserved */
		add_failure(cta, "Non-zero byte 3.");
	}

	if (dtd_start == 0) {
		return true;
	} else if (dtd_start < CTA_HEADER_SIZE || dtd_start >= size) {
		errno = EINVAL;
		return false;
	}

	i = CTA_HEADER_SIZE;
	while (i < dtd_start) {
		data_block_header = data[i];
		data_block_tag = get_bit_range(data_block_header, 7, 5);
		data_block_size = get_bit_range(data_block_header, 4, 0);

		if (i + 1 + data_block_size > dtd_start) {
			data_block_size = (uint8_t) (dtd_start - i - 1);
			if (data_block_size == 0) {
				add_failure(cta, "Data Block at offset %zu overlaps Detailed Timing "
						 "Definitions. No room for other blocks, skipping "
						 "all further Data Blocks.", i);
				break;
			}
			add_failure(cta, "Data Block at offset %zu overlaps Detailed Timing "
					 "Definitions. Adjusted its size to attempt parsing.", i);
		}

		if (!_di_cta_data_block_parse(cta, data_block_tag,
					      &data[i + 1], data_block_size, &data_block)) {
			_di_edid_cta_finish(edid_cta);
			return false;
		}

		if (data_block != NULL) {
			assert(edid_cta->data_blocks_len < EDID_CTA_MAX_DATA_BLOCKS);
			edid_cta->data_blocks[edid_cta->data_blocks_len++] = data_block;
		}

		i += 1 + data_block_size;
	}

	if (i != dtd_start)
		add_failure(cta, "Offset is %"PRIu8", but should be %zu.",
			    dtd_start, i);

	for (i = dtd_start; i + EDID_BYTE_DESCRIPTOR_SIZE <= CTA_DTD_END;
	     i += EDID_BYTE_DESCRIPTOR_SIZE) {
		/* First two bytes being zeroed is reserved by spec. */
		if (data[i] == 0 && data[i + 1] == 0)
			break;

		detailed_timing_def = _di_edid_parse_detailed_timing_def(&data[i]);
		if (!detailed_timing_def) {
			_di_edid_cta_finish(edid_cta);
			return false;
		}
		assert(edid_cta->detailed_timing_defs_len < EDID_CTA_MAX_DETAILED_TIMING_DEFS);
		edid_cta->detailed_timing_defs[edid_cta->detailed_timing_defs_len++] = detailed_timing_def;
	}

	/* All padding bytes after the last DTD must be zero */
	while (i < CTA_DTD_END) {
		if (data[i] != 0) {
			add_failure(cta, "Padding: Contains non-zero bytes.");
			break;
		}
		i++;
	}

	return true;
}

void
_di_edid_cta_finish(struct di_edid_cta *cta)
{
	size_t i;

	for (i = 0; i < cta->data_blocks_len; i++) {
		_di_cta_data_block_destroy(cta->data_blocks[i]);
	}

	for (i = 0; i < cta->detailed_timing_defs_len; i++) {
		free(cta->detailed_timing_defs[i]);
	}
}

int
di_edid_cta_get_revision(const struct di_edid_cta *cta)
{
	return cta->revision;
}

const struct di_edid_cta_flags *
di_edid_cta_get_flags(const struct di_edid_cta *cta)
{
	return &cta->flags;
}

const struct di_cta_data_block *const *
di_edid_cta_get_data_blocks(const struct di_edid_cta *cta)
{
	return (const struct di_cta_data_block *const *) cta->data_blocks;
}

enum di_cta_data_block_tag
di_cta_data_block_get_tag(const struct di_cta_data_block *block)
{
	return block->tag;
}

const struct di_cta_video_block *
di_cta_data_block_get_video(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VIDEO) {
		return NULL;
	}
	return &block->video.base;
}

const struct di_cta_ycbcr420_video_block *
di_cta_data_block_get_ycbcr420_video(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420) {
		return NULL;
	}
	return &block->ycbcr420.base;
}

const struct di_cta_video_format_pref_block *
di_cta_data_block_get_video_format_pref(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF) {
		return NULL;
	}
	return &block->video_format_pref.base;
}

const struct di_cta_audio_block *
di_cta_data_block_get_audio(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_AUDIO) {
		return NULL;
	}
	return &block->audio.audio;
}

const struct di_cta_speaker_alloc_block *
di_cta_data_block_get_speaker_alloc(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_SPEAKER_ALLOC) {
		return NULL;
	}
	return &block->speaker_alloc;
}

const struct di_cta_colorimetry_block *
di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_COLORIMETRY) {
		return NULL;
	}
	return &block->colorimetry;
}

const struct di_cta_hdr_static_metadata_block *
di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA) {
		return NULL;
	}
	return &block->hdr_static_metadata.base;
}

const struct di_cta_hdr_dynamic_metadata_block *
di_cta_data_block_get_hdr_dynamic_metadata(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA) {
		return NULL;
	}
	return &block->hdr_dynamic_metadata.base;
}

const struct di_cta_video_cap_block *
di_cta_data_block_get_video_cap(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VIDEO_CAP) {
		return NULL;
	}
	return &block->video_cap;
}

const struct di_cta_vesa_display_device_block *
di_cta_data_block_get_vesa_display_device(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE) {
		return NULL;
	}
	return &block->vesa_display_device;
}

bool
di_cta_ycbcr420_cap_map_supported(const struct di_cta_ycbcr420_cap_map_block *cap_map,
				  size_t svd_index)
{
	size_t byte, bit;

	if (cap_map->all)
		return true;

	byte = svd_index / 8;
	bit = svd_index % 8;

	if (byte >= EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES)
		return false;

	return cap_map->svd_bitmap[byte] & (1 << bit);
}

const struct di_cta_ycbcr420_cap_map_block *
di_cta_data_block_get_ycbcr420_cap_map(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP) {
		return NULL;
	}
	return &block->ycbcr420_cap_map;
}

const struct di_cta_hdmi_audio_block *
di_cta_data_block_get_hdmi_audio(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_HDMI_AUDIO) {
		return NULL;
	}
	return &block->hdmi_audio.base;
}

const struct di_cta_infoframe_block *
di_cta_data_block_get_infoframe(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_INFOFRAME) {
		return NULL;
	}
	return &block->infoframe.base;
}

const struct di_cta_speaker_location_block *
di_cta_data_block_get_speaker_locations(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_SPEAKER_LOCATION) {
		return NULL;
	}
	return &block->speaker_location.base;
}

const struct di_cta_type_vii_timing_block *
di_cta_data_block_get_did_type_vii_timing(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII) {
		return NULL;
	}
	return &block->did_vii_timing.base;
}

const struct di_cta_hdr10plus_block *
di_cta_data_block_get_hdr10plus(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_HDR10PLUS) {
		return NULL;
	}
	return &block->hdr10plus;
}

const struct di_cta_dolby_video_block *
di_cta_data_block_get_dolby_video(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_DOLBY_VIDEO) {
		return NULL;
	}
	return &block->dolby_video.base;
}

const struct di_edid_detailed_timing_def *const *
di_edid_cta_get_detailed_timing_defs(const struct di_edid_cta *cta)
{
	return (const struct di_edid_detailed_timing_def *const *) cta->detailed_timing_defs;
}

const struct di_cta_vesa_transfer_characteristics_block *
di_cta_data_block_get_vesa_transfer_characteristics(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC) {
		return NULL;
	}
	return &block->vesa_transfer_characteristics;
}

const struct di_cta_room_configuration_block *
di_cta_data_block_get_room_configuration(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_ROOM_CONFIG) {
		return NULL;
	}
	return &block->room_config;
}

const struct di_cta_vendor_hdmi_block *
di_cta_data_block_get_vendor_hdmi(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VENDOR_HDMI) {
		return NULL;
	}
	return &block->vendor_hdmi.base;
}

const struct di_cta_hdmi_forum_sink_cap *
di_cta_data_block_get_hdmi_sink_cap(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_HDMI_SINK_CAP) {
		return NULL;
	}
	return &block->hdmi_sink_cap.base;
}

const struct di_cta_vendor_hdmi_forum_block *
di_cta_data_block_get_vendor_hdmi_forum(const struct di_cta_data_block *block)
{
	if (block->tag != DI_CTA_DATA_BLOCK_VENDOR_HDMI_FORUM) {
		return NULL;
	}
	return &block->vendor_hdmi_forum.base;
}
