<commit_before>/*
 * Copyright (C) 2018 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include <string.h>
#include <xmlb.h>

#include "fu-archive.h"
#include "fu-chunk.h"
#include "fu-fastboot-device.h"

#define FASTBOOT_REMOVE_DELAY_RE_ENUMERATE	60000 /* ms */
#define FASTBOOT_TRANSACTION_TIMEOUT		1000 /* ms */
#define FASTBOOT_TRANSACTION_RETRY_MAX		600
#define FASTBOOT_EP_IN				0x81
#define FASTBOOT_EP_OUT				0x01
#define FASTBOOT_CMD_BUFSZ			64 /* bytes */

struct _FuFastbootDevice {
	FuUsbDevice			 parent_instance;
	gboolean			 secure;
	guint				 blocksz;
	guint8				 intf_nr;
};

G_DEFINE_TYPE (FuFastbootDevice, fu_fastboot_device, FU_TYPE_USB_DEVICE)

static void
fu_fastboot_device_to_string (FuDevice *device, guint idt, GString *str)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	fu_common_string_append_kx (str, idt, "InterfaceNumber", self->intf_nr);
	fu_common_string_append_kx (str, idt, "BlockSize", self->blocksz);
	fu_common_string_append_kb (str, idt, "Secure", self->secure);
}

static gboolean
fu_fastboot_device_probe (FuDevice *device, GError **error)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
	g_autoptr(GUsbInterface) intf = NULL;

	/* find the correct fastboot interface */
	intf = g_usb_device_get_interface (usb_device, 0xff, 0x42, 0x03, error);
	if (intf == NULL)
		return FALSE;
	self->intf_nr = g_usb_interface_get_number (intf);
	return TRUE;
}

static gboolean
fu_fastboot_device_open (FuDevice *device, GError **error)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));

	/* FuUsbDevice->open */
	if (!FU_DEVICE_CLASS (fu_fastboot_device_parent_class)->open (device, error))
		return FALSE;

	if (!g_usb_device_claim_interface (usb_device, self->intf_nr,
					   G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
					   error)) {
		g_prefix_error (error, "failed to claim interface: ");
		return FALSE;
	}

	/* success */
	return TRUE;
}

static void
fu_fastboot_buffer_dump (const gchar *title, const guint8 *buf, gsize sz)
{
	if (g_getenv ("FWUPD_FASTBOOT_VERBOSE") == NULL)
		return;
	g_print ("%s (%" G_GSIZE_FORMAT "):\n", title, sz);
	for (gsize i = 0; i < sz; i++) {
		g_print ("%02x[%c] ", buf[i], g_ascii_isprint (buf[i]) ? buf[i] : '?');
		if (i > 0 && (i + 1) % 256 == 0)
			g_print ("\n");
	}
	g_print ("\n");
}

static gboolean
fu_fastboot_device_write (FuDevice *device, const guint8 *buf, gsize buflen, GError **error)
{
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
	gboolean ret;
	gsize actual_len = 0;
	g_autofree guint8 *buf2 = NULL;

	/* make mutable */
	buf2 = fu_memdup_safe (buf, buflen, error);
	if (buf2 == NULL)
		return FALSE;

	fu_fastboot_buffer_dump ("writing", buf, buflen);
	ret = g_usb_device_bulk_transfer (usb_device,
					  FASTBOOT_EP_OUT,
					  buf2,
					  buflen,
					  &actual_len,
					  FASTBOOT_TRANSACTION_TIMEOUT,
					  NULL, error);
	if (!ret) {
		g_prefix_error (error, "failed to do bulk transfer: ");
		return FALSE;
	}
	if (actual_len != buflen) {
		g_set_error (error,
			     G_IO_ERROR,
			     G_IO_ERROR_INVALID_DATA,
			     "only wrote %" G_GSIZE_FORMAT "bytes", actual_len);
		return FALSE;
	}
	return TRUE;
}

static gboolean
fu_fastboot_device_writestr (FuDevice *device, const gchar *str, GError **error)
{
	gsize buflen = strlen (str);
	if (buflen > FASTBOOT_CMD_BUFSZ - 4) {
		g_set_error (error,
			     G_IO_ERROR,
			     G_IO_ERROR_INVALID_DATA,
			     "fastboot limits writes to %i bytes",
			     FASTBOOT_CMD_BUFSZ - 4);
		return FALSE;
	}
	return fu_fastboot_device_write (device, (const guint8 *) str, buflen, error);
}

typedef enum {
	FU_FASTBOOT_DEVICE_READ_FLAG_NONE,
	FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
} FuFastbootDeviceReadFlags;

static gboolean
fu_fastboot_device_read (FuDevice *device,
			 gchar **str,
			 FuFastbootDeviceReadFlags flags,
			 GError **error)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
	guint retries = 1;

	/* these commands may return INFO or take some time to complete */
	if (flags & FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL)
		retries = FASTBOOT_TRANSACTION_RETRY_MAX;

	for (guint i = 0; i < retries; i++) {
		gboolean ret;
		gsize actual_len = 0;
		guint8 buf[FASTBOOT_CMD_BUFSZ] = { 0x00 };
		g_autofree gchar *tmp = NULL;
		g_autoptr(GError) error_local = NULL;

		ret = g_usb_device_bulk_transfer (usb_device,
						  FASTBOOT_EP_IN,
						  buf,
						  sizeof(buf),
						  &actual_len,
						  FASTBOOT_TRANSACTION_TIMEOUT,
						  NULL, &error_local);
		if (!ret) {
			if (g_error_matches (error_local,
					     G_USB_DEVICE_ERROR,
					     G_USB_DEVICE_ERROR_TIMED_OUT)) {
				g_debug ("ignoring %s", error_local->message);
				continue;
			}
			g_propagate_prefixed_error (error,
						    g_steal_pointer (&error_local),
						    "failed to do bulk transfer: ");
			return FALSE;
		}
		fu_fastboot_buffer_dump ("read", buf, actual_len);
		if (actual_len < 4) {
			g_set_error (error,
				     G_IO_ERROR,
				     G_IO_ERROR_INVALID_DATA,
				     "only read %" G_GSIZE_FORMAT "bytes", actual_len);
			return FALSE;
		}

		/* info */
		tmp = g_strndup ((const gchar *) buf + 4, self->blocksz - 4);
		if (memcmp (buf, "INFO", 4) == 0) {
			if (g_strcmp0 (tmp, "erasing flash") == 0)
				fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
			else if (g_strcmp0 (tmp, "writing flash") == 0)
				fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
			else
				g_debug ("INFO returned unknown: %s", tmp);
			continue;
		}

		/* success */
		if (memcmp (buf, "OKAY", 4) == 0 || memcmp (buf, "DATA", 4) == 0) {
			if (str != NULL)
				*str = g_steal_pointer (&tmp);
			return TRUE;
		}

		/* failure */
		if (memcmp (buf, "FAIL", 4) == 0) {
			g_set_error (error,
				     G_IO_ERROR,
				     G_IO_ERROR_FAILED,
				     "failed to read response: %s", tmp);
			return FALSE;
		}

		/* unknown failure */
		g_set_error_literal (error,
				     G_IO_ERROR,
				     G_IO_ERROR_FAILED,
				     "failed to read response");
		return FALSE;
	}

	/* we timed out a *lot* */
	g_set_error_literal (error,
			     G_IO_ERROR,
			     G_IO_ERROR_FAILED,
			     "no response to read");
	return FALSE;
}

static gboolean
fu_fastboot_device_getvar (FuDevice *device, const gchar *key, gchar **str, GError **error)
{
	g_autofree gchar *tmp = g_strdup_printf ("getvar:%s", key);
	if (!fu_fastboot_device_writestr (device, tmp, error))
		return FALSE;
	if (!fu_fastboot_device_read (device, str,
				      FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error))
		return FALSE;
	return TRUE;
}

static gboolean
fu_fastboot_device_cmd (FuDevice *device, const gchar *cmd,
			FuFastbootDeviceReadFlags flags, GError **error)
{
	if (!fu_fastboot_device_writestr (device, cmd, error))<commit_msg>fastboot: Add some trivial error prefix for the getvar failure<commit_after>/*
 * Copyright (C) 2018 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include <string.h>
#include <xmlb.h>

#include "fu-archive.h"
#include "fu-chunk.h"
#include "fu-fastboot-device.h"

#define FASTBOOT_REMOVE_DELAY_RE_ENUMERATE	60000 /* ms */
#define FASTBOOT_TRANSACTION_TIMEOUT		1000 /* ms */
#define FASTBOOT_TRANSACTION_RETRY_MAX		600
#define FASTBOOT_EP_IN				0x81
#define FASTBOOT_EP_OUT				0x01
#define FASTBOOT_CMD_BUFSZ			64 /* bytes */

struct _FuFastbootDevice {
	FuUsbDevice			 parent_instance;
	gboolean			 secure;
	guint				 blocksz;
	guint8				 intf_nr;
};

G_DEFINE_TYPE (FuFastbootDevice, fu_fastboot_device, FU_TYPE_USB_DEVICE)

static void
fu_fastboot_device_to_string (FuDevice *device, guint idt, GString *str)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	fu_common_string_append_kx (str, idt, "InterfaceNumber", self->intf_nr);
	fu_common_string_append_kx (str, idt, "BlockSize", self->blocksz);
	fu_common_string_append_kb (str, idt, "Secure", self->secure);
}

static gboolean
fu_fastboot_device_probe (FuDevice *device, GError **error)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
	g_autoptr(GUsbInterface) intf = NULL;

	/* find the correct fastboot interface */
	intf = g_usb_device_get_interface (usb_device, 0xff, 0x42, 0x03, error);
	if (intf == NULL)
		return FALSE;
	self->intf_nr = g_usb_interface_get_number (intf);
	return TRUE;
}

static gboolean
fu_fastboot_device_open (FuDevice *device, GError **error)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));

	/* FuUsbDevice->open */
	if (!FU_DEVICE_CLASS (fu_fastboot_device_parent_class)->open (device, error))
		return FALSE;

	if (!g_usb_device_claim_interface (usb_device, self->intf_nr,
					   G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
					   error)) {
		g_prefix_error (error, "failed to claim interface: ");
		return FALSE;
	}

	/* success */
	return TRUE;
}

static void
fu_fastboot_buffer_dump (const gchar *title, const guint8 *buf, gsize sz)
{
	if (g_getenv ("FWUPD_FASTBOOT_VERBOSE") == NULL)
		return;
	g_print ("%s (%" G_GSIZE_FORMAT "):\n", title, sz);
	for (gsize i = 0; i < sz; i++) {
		g_print ("%02x[%c] ", buf[i], g_ascii_isprint (buf[i]) ? buf[i] : '?');
		if (i > 0 && (i + 1) % 256 == 0)
			g_print ("\n");
	}
	g_print ("\n");
}

static gboolean
fu_fastboot_device_write (FuDevice *device, const guint8 *buf, gsize buflen, GError **error)
{
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
	gboolean ret;
	gsize actual_len = 0;
	g_autofree guint8 *buf2 = NULL;

	/* make mutable */
	buf2 = fu_memdup_safe (buf, buflen, error);
	if (buf2 == NULL)
		return FALSE;

	fu_fastboot_buffer_dump ("writing", buf, buflen);
	ret = g_usb_device_bulk_transfer (usb_device,
					  FASTBOOT_EP_OUT,
					  buf2,
					  buflen,
					  &actual_len,
					  FASTBOOT_TRANSACTION_TIMEOUT,
					  NULL, error);
	if (!ret) {
		g_prefix_error (error, "failed to do bulk transfer: ");
		return FALSE;
	}
	if (actual_len != buflen) {
		g_set_error (error,
			     G_IO_ERROR,
			     G_IO_ERROR_INVALID_DATA,
			     "only wrote %" G_GSIZE_FORMAT "bytes", actual_len);
		return FALSE;
	}
	return TRUE;
}

static gboolean
fu_fastboot_device_writestr (FuDevice *device, const gchar *str, GError **error)
{
	gsize buflen = strlen (str);
	if (buflen > FASTBOOT_CMD_BUFSZ - 4) {
		g_set_error (error,
			     G_IO_ERROR,
			     G_IO_ERROR_INVALID_DATA,
			     "fastboot limits writes to %i bytes",
			     FASTBOOT_CMD_BUFSZ - 4);
		return FALSE;
	}
	return fu_fastboot_device_write (device, (const guint8 *) str, buflen, error);
}

typedef enum {
	FU_FASTBOOT_DEVICE_READ_FLAG_NONE,
	FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL,
} FuFastbootDeviceReadFlags;

static gboolean
fu_fastboot_device_read (FuDevice *device,
			 gchar **str,
			 FuFastbootDeviceReadFlags flags,
			 GError **error)
{
	FuFastbootDevice *self = FU_FASTBOOT_DEVICE (device);
	GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
	guint retries = 1;

	/* these commands may return INFO or take some time to complete */
	if (flags & FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL)
		retries = FASTBOOT_TRANSACTION_RETRY_MAX;

	for (guint i = 0; i < retries; i++) {
		gboolean ret;
		gsize actual_len = 0;
		guint8 buf[FASTBOOT_CMD_BUFSZ] = { 0x00 };
		g_autofree gchar *tmp = NULL;
		g_autoptr(GError) error_local = NULL;

		ret = g_usb_device_bulk_transfer (usb_device,
						  FASTBOOT_EP_IN,
						  buf,
						  sizeof(buf),
						  &actual_len,
						  FASTBOOT_TRANSACTION_TIMEOUT,
						  NULL, &error_local);
		if (!ret) {
			if (g_error_matches (error_local,
					     G_USB_DEVICE_ERROR,
					     G_USB_DEVICE_ERROR_TIMED_OUT)) {
				g_debug ("ignoring %s", error_local->message);
				continue;
			}
			g_propagate_prefixed_error (error,
						    g_steal_pointer (&error_local),
						    "failed to do bulk transfer: ");
			return FALSE;
		}
		fu_fastboot_buffer_dump ("read", buf, actual_len);
		if (actual_len < 4) {
			g_set_error (error,
				     G_IO_ERROR,
				     G_IO_ERROR_INVALID_DATA,
				     "only read %" G_GSIZE_FORMAT "bytes", actual_len);
			return FALSE;
		}

		/* info */
		tmp = g_strndup ((const gchar *) buf + 4, self->blocksz - 4);
		if (memcmp (buf, "INFO", 4) == 0) {
			if (g_strcmp0 (tmp, "erasing flash") == 0)
				fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
			else if (g_strcmp0 (tmp, "writing flash") == 0)
				fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
			else
				g_debug ("INFO returned unknown: %s", tmp);
			continue;
		}

		/* success */
		if (memcmp (buf, "OKAY", 4) == 0 || memcmp (buf, "DATA", 4) == 0) {
			if (str != NULL)
				*str = g_steal_pointer (&tmp);
			return TRUE;
		}

		/* failure */
		if (memcmp (buf, "FAIL", 4) == 0) {
			g_set_error (error,
				     G_IO_ERROR,
				     G_IO_ERROR_FAILED,
				     "failed to read response: %s", tmp);
			return FALSE;
		}

		/* unknown failure */
		g_set_error_literal (error,
				     G_IO_ERROR,
				     G_IO_ERROR_FAILED,
				     "failed to read response");
		return FALSE;
	}

	/* we timed out a *lot* */
	g_set_error_literal (error,
			     G_IO_ERROR,
			     G_IO_ERROR_FAILED,
			     "no response to read");
	return FALSE;
}

static gboolean
fu_fastboot_device_getvar (FuDevice *device, const gchar *key, gchar **str, GError **error)
{
	g_autofree gchar *tmp = g_strdup_printf ("getvar:%s", key);
	if (!fu_fastboot_device_writestr (device, tmp, error))
		return FALSE;
	if (!fu_fastboot_device_read (device, str,
				      FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error)) {
		g_prefix_error (error, "failed to getvar %s: ", key);
		return FALSE;
	}
	return TRUE;
}

static gboolean
fu_fastboot_device_cmd (FuDevice *device, const gchar *cmd,
			FuFastbootDeviceReadFlags flags, GError **error)
{
	if (!fu_fastboot_device_writestr (device, cmd, error))
{'fraction_non_alphanumeric': 0.11605745982079363, 'fraction_numerical': 0.010809273218603328, 'mean_word_length': 3.9430579964850616, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, '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': '5270394', 'n_tokens_mistral': 6155, 'n_tokens_neox': 5415, 'n_words': 1229}