<commit_before>/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2015-2016 <NAME> <<EMAIL>>
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * 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 "config.h"

#include <string.h>

#include "dfu-element.h"
#include "dfu-error.h"
#include "dfu-format-ihex.h"
#include "dfu-image.h"

/**
 * dfu_firmware_detect_ihex: (skip)
 * @bytes: data to parse
 *
 * Attempts to sniff the data and work out the firmware format
 *
 * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW
 **/
DfuFirmwareFormat
dfu_firmware_detect_ihex (GBytes *bytes)
{
	guint8 *data;
	gsize len;
	data = (guint8 *) g_bytes_get_data (bytes, &len);
	if (len < 12)
		return DFU_FIRMWARE_FORMAT_UNKNOWN;
	if (data[0] != ':')
		return DFU_FIRMWARE_FORMAT_UNKNOWN;
	return DFU_FIRMWARE_FORMAT_INTEL_HEX;
}

static guint8
dfu_firmware_ihex_parse_uint8 (const gchar *data, guint pos)
{
	gchar buffer[3];
	memcpy (buffer, data + pos, 2);
	buffer[2] = '\0';
	return (guint8) g_ascii_strtoull (buffer, NULL, 16);
}

static guint16
dfu_firmware_ihex_parse_uint16 (const gchar *data, guint pos)
{
	gchar buffer[5];
	memcpy (buffer, data + pos, 4);
	buffer[4] = '\0';
	return (guint16) g_ascii_strtoull (buffer, NULL, 16);
}

#define	DFU_INHX32_RECORD_TYPE_DATA		0
#define	DFU_INHX32_RECORD_TYPE_EOF		1
#define	DFU_INHX32_RECORD_TYPE_EXTENDED		4

/**
 * dfu_firmware_from_ihex: (skip)
 * @firmware: a #DfuFirmware
 * @bytes: data to parse
 * @flags: some #DfuFirmwareParseFlags
 * @error: a #GError, or %NULL
 *
 * Unpacks into a firmware object from raw data.
 *
 * Returns: %TRUE for success
 **/
gboolean
dfu_firmware_from_ihex (DfuFirmware *firmware,
			GBytes *bytes,
			DfuFirmwareParseFlags flags,
			GError **error)
{
	const gchar *in_buffer;
	gsize len_in;
	guint16 addr_high = 0;
	guint16 addr_low = 0;
	guint32 addr32 = 0;
	guint32 addr32_last = 0;
	guint8 checksum;
	guint8 data_tmp;
	guint8 len_tmp;
	guint8 type;
	guint end;
	guint i;
	guint j;
	guint offset = 0;
	g_autoptr(DfuElement) element = NULL;
	g_autoptr(DfuImage) image = NULL;
	g_autoptr(GBytes) contents = NULL;
	g_autoptr(GString) string = NULL;

	g_return_val_if_fail (bytes != NULL, FALSE);

	/* create element */
	image = dfu_image_new ();
	dfu_image_set_name (image, "ihex");
	element = dfu_element_new ();

	/* parse records */
	in_buffer = g_bytes_get_data (bytes, &len_in);
	string = g_string_new ("");
	while (offset < len_in) {

		/* check starting token */
		if (in_buffer[offset] != ':') {
			g_set_error (error,
				     DFU_ERROR,
				     DFU_ERROR_INVALID_FILE,
				     "invalid starting token, got %c at %x",
				     in_buffer[offset], offset);
			return FALSE;
		}

		/* check there's enough data for the smallest possible record */
		if (offset + 12 > (guint) len_in) {
			g_set_error (error,
				     DFU_ERROR,
				     DFU_ERROR_INVALID_FILE,
				     "record incomplete at %u, length %u",
				     offset, (guint) len_in);
			return FALSE;
		}

		/* length, 16-bit address, type */
		len_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+1);
		addr_low = dfu_firmware_ihex_parse_uint16 (in_buffer, offset+3);
		type = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+7);

		/* position of checksum */
		end = offset + 9 + len_tmp * 2;
		if (end > (guint) len_in) {
			g_set_error (error,
				     DFU_ERROR,
				     DFU_ERROR_INVALID_FILE,
				     "checksum > file length: %u",
				     end);
			return FALSE;
		}

		/* verify checksum */
		if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) {
			checksum = 0;
			for (i = offset + 1; i < end + 2; i += 2) {
				data_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, i);
				checksum += data_tmp;
			}
			if (checksum != 0)  {
				g_set_error_literal (error,
						     DFU_ERROR,
						     DFU_ERROR_INVALID_FILE,
						     "invalid record checksum");
				return FALSE;
			}
		}

		/* process different record types */
		switch (type) {
		case DFU_INHX32_RECORD_TYPE_DATA:
			/* if not contiguous with previous record */
			if ((addr_high + addr_low) != addr32) {
				if (addr32 == 0x0) {
					g_debug ("base address %04x", addr_low);
					dfu_element_set_address (element, addr_low);
				}
//				addr32 = addr_high + addr_low;
				addr32 = ((guint32) addr_high << 16) + addr_low;
			}

			/* parse bytes from line */
			for (i = offset + 9; i < end; i += 2) {
				/* any holes in the hex record */
				guint32 len_hole = addr32 - addr32_last;
				if (addr32_last > 0x0 && len_hole > 1) {
					for (j = 1; j < len_hole; j++) {
						g_debug ("filling address 0x%04x",
							 addr32_last + j);
						/* although 0xff might be clearer,
						 * we can't write 0xffff to pic14 */<commit_msg>libdfu: Fix hang when parsing corrupt IHEX files<commit_after>/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2015-2016 <NAME> <<EMAIL>>
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * 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 "config.h"

#include <string.h>

#include "dfu-element.h"
#include "dfu-error.h"
#include "dfu-format-ihex.h"
#include "dfu-image.h"

/**
 * dfu_firmware_detect_ihex: (skip)
 * @bytes: data to parse
 *
 * Attempts to sniff the data and work out the firmware format
 *
 * Returns: a #DfuFirmwareFormat, e.g. %DFU_FIRMWARE_FORMAT_RAW
 **/
DfuFirmwareFormat
dfu_firmware_detect_ihex (GBytes *bytes)
{
	guint8 *data;
	gsize len;
	data = (guint8 *) g_bytes_get_data (bytes, &len);
	if (len < 12)
		return DFU_FIRMWARE_FORMAT_UNKNOWN;
	if (data[0] != ':')
		return DFU_FIRMWARE_FORMAT_UNKNOWN;
	return DFU_FIRMWARE_FORMAT_INTEL_HEX;
}

static guint8
dfu_firmware_ihex_parse_uint8 (const gchar *data, guint pos)
{
	gchar buffer[3];
	memcpy (buffer, data + pos, 2);
	buffer[2] = '\0';
	return (guint8) g_ascii_strtoull (buffer, NULL, 16);
}

static guint16
dfu_firmware_ihex_parse_uint16 (const gchar *data, guint pos)
{
	gchar buffer[5];
	memcpy (buffer, data + pos, 4);
	buffer[4] = '\0';
	return (guint16) g_ascii_strtoull (buffer, NULL, 16);
}

#define	DFU_INHX32_RECORD_TYPE_DATA		0
#define	DFU_INHX32_RECORD_TYPE_EOF		1
#define	DFU_INHX32_RECORD_TYPE_EXTENDED		4

/**
 * dfu_firmware_from_ihex: (skip)
 * @firmware: a #DfuFirmware
 * @bytes: data to parse
 * @flags: some #DfuFirmwareParseFlags
 * @error: a #GError, or %NULL
 *
 * Unpacks into a firmware object from raw data.
 *
 * Returns: %TRUE for success
 **/
gboolean
dfu_firmware_from_ihex (DfuFirmware *firmware,
			GBytes *bytes,
			DfuFirmwareParseFlags flags,
			GError **error)
{
	const gchar *in_buffer;
	gsize len_in;
	guint16 addr_high = 0;
	guint16 addr_low = 0;
	guint32 addr32 = 0;
	guint32 addr32_last = 0;
	guint8 checksum;
	guint8 data_tmp;
	guint8 len_tmp;
	guint8 type;
	guint end;
	guint i;
	guint j;
	guint offset = 0;
	g_autoptr(DfuElement) element = NULL;
	g_autoptr(DfuImage) image = NULL;
	g_autoptr(GBytes) contents = NULL;
	g_autoptr(GString) string = NULL;

	g_return_val_if_fail (bytes != NULL, FALSE);

	/* create element */
	image = dfu_image_new ();
	dfu_image_set_name (image, "ihex");
	element = dfu_element_new ();

	/* parse records */
	in_buffer = g_bytes_get_data (bytes, &len_in);
	string = g_string_new ("");
	while (offset < len_in) {

		/* check starting token */
		if (in_buffer[offset] != ':') {
			g_set_error (error,
				     DFU_ERROR,
				     DFU_ERROR_INVALID_FILE,
				     "invalid starting token, got %c at %x",
				     in_buffer[offset], offset);
			return FALSE;
		}

		/* check there's enough data for the smallest possible record */
		if (offset + 12 > (guint) len_in) {
			g_set_error (error,
				     DFU_ERROR,
				     DFU_ERROR_INVALID_FILE,
				     "record incomplete at %u, length %u",
				     offset, (guint) len_in);
			return FALSE;
		}

		/* length, 16-bit address, type */
		len_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+1);
		addr_low = dfu_firmware_ihex_parse_uint16 (in_buffer, offset+3);
		type = dfu_firmware_ihex_parse_uint8 (in_buffer, offset+7);

		/* position of checksum */
		end = offset + 9 + len_tmp * 2;
		if (end > (guint) len_in) {
			g_set_error (error,
				     DFU_ERROR,
				     DFU_ERROR_INVALID_FILE,
				     "checksum > file length: %u",
				     end);
			return FALSE;
		}

		/* verify checksum */
		if ((flags & DFU_FIRMWARE_PARSE_FLAG_NO_CRC_TEST) == 0) {
			checksum = 0;
			for (i = offset + 1; i < end + 2; i += 2) {
				data_tmp = dfu_firmware_ihex_parse_uint8 (in_buffer, i);
				checksum += data_tmp;
			}
			if (checksum != 0)  {
				g_set_error_literal (error,
						     DFU_ERROR,
						     DFU_ERROR_INVALID_FILE,
						     "invalid record checksum");
				return FALSE;
			}
		}

		/* process different record types */
		switch (type) {
		case DFU_INHX32_RECORD_TYPE_DATA:
			/* if not contiguous with previous record */
			if ((addr_high + addr_low) != addr32) {
				if (addr32 == 0x0) {
					g_debug ("base address %04x", addr_low);
					dfu_element_set_address (element, addr_low);
				}
//				addr32 = addr_high + addr_low;
				addr32 = ((guint32) addr_high << 16) + addr_low;
			}

			/* does not make sense */
			if (addr32 < addr32_last) {
				g_set_error (error,
					     DFU_ERROR,
					     DFU_ERROR_INVALID_FILE,
					     "invalid address 0x%x, last was 0x%x",
					     (guint) addr32,
					     (guint) addr32_last);
				return FALSE;
			}

			/* parse bytes from line */
			for (i = offset + 9; i < end; i += 2) {
				/* any holes in the hex record */
				guint32 len_hole = addr32 - addr32_last;
				if (addr32_last > 0x0 && len_hole > 1) {
					for (j = 1; j < len_hole; j++) {
						g_debug ("filling address 0x%04x",
							 addr32_last + j);
						/* although 0xff might be clearer,
						 * we can't write 0xffff to pic14 */
{'fraction_non_alphanumeric': 0.11193695710575803, 'fraction_numerical': 0.02632757231127429, 'mean_word_length': 3.345525291828794, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1171768', 'n_tokens_mistral': 4796, 'n_tokens_neox': 4110, 'n_words': 1174}