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

#include "config.h"

#include "fu-logitech-hidpp-common.h"
#include "fu-logitech-hidpp-device.h"
#include "fu-logitech-hidpp-hidpp.h"
#include "fu-logitech-hidpp-radio.h"
#include "fu-logitech-hidpp-runtime-bolt.h"

struct _FuLogitechHidPpRuntimeBolt {
	FuLogitechHidPpRuntime parent_instance;
	guint8 pairing_slots;
};

G_DEFINE_TYPE(FuLogitechHidPpRuntimeBolt, fu_logitech_hidpp_runtime_bolt, FU_TYPE_HIDPP_RUNTIME)

static gboolean
fu_logitech_hidpp_runtime_bolt_detach(FuDevice *device, FuProgress *progress, GError **error)
{
	FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
	g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
	g_autoptr(GError) error_local = NULL;
	g_autoptr(FwupdRequest) request = fwupd_request_new();

	msg->report_id = HIDPP_REPORT_ID_LONG;
	msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
	msg->sub_id = HIDPP_SUBID_SET_LONG_REGISTER;
	msg->function_id = BOLT_REGISTER_DFU_CONTROL;
	msg->data[0] = 1; /* Enable DFU */
	msg->data[4] = 'P';
	msg->data[5] = 'R';
	msg->data[6] = 'E';
	msg->hidpp_version = 1;
	msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT;
	if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self),
				    msg,
				    FU_UNIFYING_DEVICE_TIMEOUT_MS,
				    &error_local)) {
		if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) {
			g_debug("failed to detach to bootloader: %s", error_local->message);
		} else {
			g_prefix_error(&error_local, "failed to detach to bootloader: ");
			g_propagate_error(error, g_steal_pointer(&error_local));
			return FALSE;
		}
	}

	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	/* generate a message if not already set */
	if (fu_device_get_update_message(device) == NULL) {
		g_autofree gchar *str = NULL;
		str = g_strdup_printf("%s needs to be manually restarted to complete the update. "
				      "Please unplug it and plug it back again.",
				      fu_device_get_name(device));
		fu_device_set_update_message(device, str);
	}
	fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE);
	fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG);
	fwupd_request_set_message(request, fu_device_get_update_message(device));
	fu_device_emit_request(device, request);
	return TRUE;
}

static void
fu_logitech_hidpp_runtime_bolt_to_string(FuDevice *device, guint idt, GString *str)
{
	FuLogitechHidPpRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device);

	FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_bolt_parent_class)->to_string(device, idt, str);
	fu_common_string_append_ku(str, idt, "PairingSlots", self->pairing_slots);
}

static FuLogitechHidPpDevice *
fu_logitech_hidpp_runtime_bolt_find_paired_device(FuDevice *device, guint16 hidpp_pid)
{
	GPtrArray *children = fu_device_get_children(device);

	for (guint i = 0; i < children->len; i++) {
		FuDevice *child = g_ptr_array_index(children, i);
		if (FU_IS_HIDPP_DEVICE(child) &&
		    fu_logitech_hidpp_device_get_hidpp_pid(FU_HIDPP_DEVICE(child)) == hidpp_pid)
			return FU_HIDPP_DEVICE(g_object_ref(child));
	}

	return NULL;
}

static gchar *
fu_logitech_hidpp_runtime_bolt_query_device_name(FuLogitechHidPpRuntime *self,
						 guint8 slot,
						 GError **error)
{
	g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
	GString *dev_name = g_string_new(NULL);
	guint namelen;

	msg->report_id = HIDPP_REPORT_ID_SHORT;
	msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
	msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
	msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION;
	msg->data[0] = 0x60 | slot; /* device name */
	msg->data[1] = 1;
	msg->hidpp_version = 1;
	if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
					msg,
					error)) {
		g_prefix_error(error, "failed to retrieve the device name for slot %d: ", slot);
		return NULL;
	}
	namelen = msg->data[2];
	g_string_append_len(dev_name, (const char *)(&(msg->data[3])), namelen);

	return g_string_free(dev_name, FALSE);
}

static gboolean
fu_logitech_hidpp_runtime_bolt_update_paired_device(FuLogitechHidPpRuntimeBolt *self,
						    FuLogitechHidPpHidppMsg *msg,
						    GError **error)
{
	FuLogitechHidPpRuntime *runtime = FU_HIDPP_RUNTIME(self);
	gboolean reachable = FALSE;
	guint16 hidpp_pid;
	g_autoptr(FuLogitechHidPpDevice) child = NULL;

	if ((msg->data[0] & 0x40) == 0)
		reachable = TRUE;
	hidpp_pid = (msg->data[1] << 8) | msg->data[2];

	child = fu_logitech_hidpp_runtime_bolt_find_paired_device(FU_DEVICE(self), hidpp_pid);
	if (child != NULL) {
		g_debug("%s [%s] is reachable:%i",
			fu_device_get_name(FU_DEVICE(child)),
			fu_device_get_name(FU_DEVICE(child)),
			reachable);
		if (reachable) {
			g_autoptr(FuDeviceLocker) locker = NULL;

			/* known paired & reachable */
			fu_device_probe_invalidate(FU_DEVICE(child));
			locker = fu_device_locker_new(FU_DEVICE(child), error);
			if (locker == NULL) {
				g_prefix_error(error, "cannot rescan paired device: ");
				return FALSE;
			}
			fu_device_remove_flag(FU_DEVICE(child), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
		} else {
			GPtrArray *children = NULL;
			/* any successful 'ping' will clear this */
			fu_device_add_flag(FU_DEVICE(child), FWUPD_DEVICE_FLAG_UNREACHABLE);
			fu_device_inhibit(FU_DEVICE(child), "unreachable", "device is unreachable");
			children = fu_device_get_children(FU_DEVICE(child));
			for (guint i = 0; i < children->len; i++) {
				FuDevice *radio = g_ptr_array_index(children, i);
				fu_device_add_flag(radio, FWUPD_DEVICE_FLAG_UNREACHABLE);
				fu_device_inhibit(radio, "unreachable", "device is unreachable");
			}
		}
	} else if (reachable) {
		g_autofree gchar *name = NULL;

		/* unknown paired device, reachable state */
		name = fu_logitech_hidpp_runtime_bolt_query_device_name(runtime,
									msg->device_id,
									error);
		if (name == NULL)
			return FALSE;
		child = fu_logitech_hidpp_device_new(FU_UDEV_DEVICE(self));
		fu_device_set_name(FU_DEVICE(child), name);
		fu_logitech_hidpp_device_set_device_idx(child, msg->device_id);
		fu_logitech_hidpp_device_set_hidpp_pid(child, hidpp_pid);
		if (!fu_device_probe(FU_DEVICE(child), error))
			return FALSE;
		if (!fu_device_setup(FU_DEVICE(child), error))
			return FALSE;
		fu_device_add_child(FU_DEVICE(self), FU_DEVICE(child));
	} else {
		/* unknown paired device, unreachable state */
		g_warning("unknown paired device 0x%0x in slot %d (unreachable)",
			  hidpp_pid,
			  msg->device_id);
	}

	return TRUE;
}

void
fu_logitech_hidpp_runtime_bolt_poll_peripherals(FuDevice *device)
{
	FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
	FuLogitechHidPpRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device);

	for (guint i = 1; i <= bolt->pairing_slots; i++) {
		g_autofree gchar *name = NULL;
		g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
		g_autoptr(GError) error_local = NULL;
		guint16 hidpp_pid;

		name = fu_logitech_hidpp_runtime_bolt_query_device_name(self, i, &error_local);
		if (name == NULL) {
			g_debug("Can't query paired device name for slot %u", i);
			continue;
		}

		msg->report_id = HIDPP_REPORT_ID_SHORT;
		msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
		msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
		msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION;
		msg->data[0] = 0x50 | i; /* pairing information */
		msg->hidpp_version = 1;
		if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
						msg,
						&error_local))
			continue;
		hidpp_pid = (msg->data[2] << 8) | msg->data[3];
		if ((msg->data[1] & 0x40) == 0) {
			/* paired device is reachable */
			g_autoptr(FuLogitechHidPpDevice) child = NULL;
			child = fu_logitech_hidpp_device_new(FU_UDEV_DEVICE(device));
			fu_device_set_install_duration(FU_DEVICE(child), 270);
			fu_device_add_private_flag(FU_DEVICE(child),
						   FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO);
			fu_device_set_name(FU_DEVICE(child), name);
			fu_logitech_hidpp_device_set_device_idx(child, i);
			fu_logitech_hidpp_device_set_hidpp_pid(child, hidpp_pid);
			if (!fu_device_probe(FU_DEVICE(child), &error_local))
				continue;
			if (!fu_device_setup(FU_DEVICE(child), &error_local))
				continue;
			fu_device_add_child(device, FU_DEVICE(child));
		}
	}
}

static gboolean
fu_logitech_hidpp_runtime_bolt_process_notification(FuLogitechHidPpRuntimeBolt *self,
						    FuLogitechHidPpHidppMsg *msg)
{
	g_autoptr(GError) error_local = NULL;

	/* HID++1.0 error */
	if (!fu_logitech_hidpp_msg_is_error(msg, &error_local)) {
		g_warning("failed to get pending read: %s", error_local->message);
		return TRUE;
	}

	/* unifying receiver notification */
	if (msg->report_id == HIDPP_REPORT_ID_SHORT) {
		switch (msg->sub_id) {
		case HIDPP_SUBID_DEVICE_CONNECTION:
		case HIDPP_SUBID_DEVICE_DISCONNECTION:
		case HIDPP_SUBID_DEVICE_LOCKING_CHANGED:
			if (!fu_logitech_hidpp_runtime_bolt_update_paired_device(self,
										 msg,
										 &error_local)) {
				g_warning("failed to update paired device status: %s",
					  error_local->message);
				return FALSE;
			}
			break;
		case HIDPP_SUBID_LINK_QUALITY:
			g_debug("ignoring link quality message");
			break;
		case HIDPP_SUBID_ERROR_MSG:
			g_debug("ignoring link quality message");
			break;
		default:
			g_debug("unknown SubID %02x", msg->sub_id);
			break;
		}
	}

	return TRUE;
}

static FuLogitechHidPpHidppMsg *
fu_logitech_hidpp_runtime_bolt_find_newest_msg(GPtrArray *msgs, guint8 device_id, guint8 sub_id)
{
	for (guint i = 0; i < msgs->len; i++) {
		FuLogitechHidPpHidppMsg *msg = g_ptr_array_index(msgs, msgs->len - (i + 1));
		if (msg->device_id == device_id && msg->sub_id == sub_id)
			return msg;
	}
	return NULL;
}

static gboolean
fu_logitech_hidpp_runtime_bolt_poll(FuDevice *device, GError **error)
{
	FuLogitechHidPpRuntime *runtime = FU_HIDPP_RUNTIME(device);
	FuLogitechHidPpRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device);
	const guint timeout = 1; /* ms */
	g_autoptr(GPtrArray) msgs = g_ptr_array_new_with_free_func(g_free);

	/* open -- not a locker as we have no kernel driver */
	if (!fu_device_open(device, error))
		return FALSE;

	/* drain all the pending messages into the array */
	while (TRUE) {
		g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
		g_autoptr(GError) error_local = NULL;
		msg->hidpp_version = 1;
		if (!fu_logitech_hidpp_receive(fu_logitech_hidpp_runtime_get_io_channel(runtime),
					       msg,
					       timeout,
					       &error_local)) {
			if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
				break;
			g_propagate_prefixed_error(error,
						   g_steal_pointer(&error_local),
						   "error polling Bolt receiver: ");
			return FALSE;
		}
		g_ptr_array_add(msgs, g_steal_pointer(&msg));
	}

	/* process messages in order, but discard any message with a newer version */
	for (guint i = 0; i < msgs->len; i++) {
		FuLogitechHidPpHidppMsg *msg = g_ptr_array_index(msgs, i);
		FuLogitechHidPpHidppMsg *msg_newest;

		/* find the newest message with the matching device and sub-IDs */
		msg_newest = fu_logitech_hidpp_runtime_bolt_find_newest_msg(msgs,
									    msg->device_id,
									    msg->sub_id);
		if (msg != msg_newest) {
			g_debug("ignoring duplicate message device-id:%02x [%s] sub-id:%02x [%s]",
				msg->device_id,
				fu_logitech_hidpp_msg_dev_id_to_string(msg),
				msg->sub_id,
				fu_logitech_hidpp_msg_sub_id_to_string(msg));
			continue;
		}
		fu_logitech_hidpp_runtime_bolt_process_notification(self, msg);
	}

	return TRUE;
}

static gboolean
fu_logitech_hidpp_runtime_bolt_setup_internal(FuDevice *device, GError **error)
{
	FuContext *ctx = fu_device_get_context(device);
	FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
	FuLogitechHidPpRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device);
	g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();

	msg->report_id = HIDPP_REPORT_ID_SHORT;
	msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
	msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
	msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION;
	msg->data[0] = 0x02; /* FW Version (contains the number of pairing slots) */
	msg->hidpp_version = 1;
	if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
					msg,
					error)) {
		g_prefix_error(error, "failed to fetch the number of pairing slots: ");
		return FALSE;
	}
	bolt->pairing_slots = msg->data[8];

	/*
	 * TODO: Iterate only over the first three entity indexes for
	 * now.
	 */
	for (guint i = 0; i < 3; i++) {
		g_autofree gchar *version = NULL;
		g_autoptr(FuLogitechHidPpRadio) radio = NULL;
		g_autofree gchar *instance_id = NULL;
		g_autoptr(GString) radio_version = NULL;

		msg->report_id = HIDPP_REPORT_ID_SHORT;
		msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
		msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
		msg->function_id = BOLT_REGISTER_RECEIVER_FW_INFORMATION;
		msg->data[0] = i;
		msg->hidpp_version = 1;
		if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
						msg,
						error)) {
			g_prefix_error(error, "failed to read device config: ");
			return FALSE;
		}
		switch (msg->data[0]) {
		case 0:
			/* main application */
			version = fu_logitech_hidpp_format_version("MPR",
								   msg->data[1],
								   msg->data[2],
								   (guint16)msg->data[3] << 8 |
								       msg->data[4]);
			fu_device_set_version(device, version);
			break;
		case 1:
			/* bootloader */
			version = fu_logitech_hidpp_format_version("BOT",
								   msg->data[1],
								   msg->data[2],
								   (guint16)msg->data[3] << 8 |
								       msg->data[4]);
			fu_device_set_version_bootloader(device, version);
			break;
		case 5:
			/* SoftDevice */
			radio_version = g_string_new(NULL);
			radio = fu_logitech_hidpp_radio_new(ctx, i);
			fu_device_set_physical_id(FU_DEVICE(radio),
						  fu_device_get_physical_id(device));
			fu_device_set_logical_id(FU_DEVICE(radio), "Receiver_SoftDevice");

			instance_id =
			    g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&ENT_05",
					    fu_udev_device_get_vendor(FU_UDEV_DEVICE(device)),
					    fu_udev_device_get_model(FU_UDEV_DEVICE(device)));
			fu_device_add_guid(FU_DEVICE(radio), instance_id);
			g_string_append_printf(radio_version,
					       "0x%.4x",
					       (guint16)(msg->data[3] << 8 | msg->data[4]));
			fu_device_set_version(FU_DEVICE(radio), radio_version->str);
			fu_device_add_child(device, FU_DEVICE(radio));
			break;
		default:
			break;
		}
	}

	/* enable HID++ notifications */
	if (!fu_logitech_hidpp_runtime_enable_notifications(self, error)) {
		g_prefix_error(error, "failed to enable notifications: ");
		return FALSE;
	}
	fu_logitech_hidpp_runtime_bolt_poll_peripherals(device);

	/* success */
	return TRUE;
}

static gboolean
fu_logitech_hidpp_runtime_bolt_setup(FuDevice *device, GError **error)
{
	g_autoptr(GError) error_local = NULL;
	for (guint i = 0; i < 5; i++) {
		g_clear_error(&error_local);
		/* HID++1.0 devices have to sleep to allow Solaar to talk to
		 * the device first -- we can't use the SwID as this is a
		 * HID++2.0 feature */
		g_usleep(200 * 1000);
		if (fu_logitech_hidpp_runtime_bolt_setup_internal(device, &error_local))
			return TRUE;
		if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) {
			g_propagate_error(error, g_steal_pointer(&error_local));
			return FALSE;
		}
	}
	g_propagate_error(error, g_steal_pointer(&error_local));
	return FALSE;
}

static void
fu_logitech_hidpp_runtime_bolt_class_init(FuLogitechHidPpRuntimeBoltClass *klass)
{
	FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);

	klass_device->detach = fu_logitech_hidpp_runtime_bolt_detach;
	klass_device->setup = fu_logitech_hidpp_runtime_bolt_setup;
	klass_device->poll = fu_logitech_hidpp_runtime_bolt_poll;
	klass_device->to_string = fu_logitech_hidpp_runtime_bolt_to_string;
}

static void
fu_logitech_hidpp_runtime_bolt_init(FuLogitechHidPpRuntimeBolt *self)
{
	fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG);
	fu_device_set_name(FU_DEVICE(self), "Bolt Receiver");
	fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned");
}
<commit_msg>logitech-hidpp: remove user action message from Bolt receiver update<commit_after>/*
 * Copyright (C) 2021 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include "fu-logitech-hidpp-common.h"
#include "fu-logitech-hidpp-device.h"
#include "fu-logitech-hidpp-hidpp.h"
#include "fu-logitech-hidpp-radio.h"
#include "fu-logitech-hidpp-runtime-bolt.h"

struct _FuLogitechHidPpRuntimeBolt {
	FuLogitechHidPpRuntime parent_instance;
	guint8 pairing_slots;
};

G_DEFINE_TYPE(FuLogitechHidPpRuntimeBolt, fu_logitech_hidpp_runtime_bolt, FU_TYPE_HIDPP_RUNTIME)

static gboolean
fu_logitech_hidpp_runtime_bolt_detach(FuDevice *device, FuProgress *progress, GError **error)
{
	FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
	g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
	g_autoptr(GError) error_local = NULL;

	msg->report_id = HIDPP_REPORT_ID_LONG;
	msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
	msg->sub_id = HIDPP_SUBID_SET_LONG_REGISTER;
	msg->function_id = BOLT_REGISTER_DFU_CONTROL;
	msg->data[0] = 1; /* Enable DFU */
	msg->data[4] = 'P';
	msg->data[5] = 'R';
	msg->data[6] = 'E';
	msg->hidpp_version = 1;
	msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT;
	if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self),
				    msg,
				    FU_UNIFYING_DEVICE_TIMEOUT_MS,
				    &error_local)) {
		if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) {
			g_debug("failed to detach to bootloader: %s", error_local->message);
		} else {
			g_prefix_error(&error_local, "failed to detach to bootloader: ");
			g_propagate_error(error, g_steal_pointer(&error_local));
			return FALSE;
		}
	}
	fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
	return TRUE;
}

static void
fu_logitech_hidpp_runtime_bolt_to_string(FuDevice *device, guint idt, GString *str)
{
	FuLogitechHidPpRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device);

	FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_bolt_parent_class)->to_string(device, idt, str);
	fu_common_string_append_ku(str, idt, "PairingSlots", self->pairing_slots);
}

static FuLogitechHidPpDevice *
fu_logitech_hidpp_runtime_bolt_find_paired_device(FuDevice *device, guint16 hidpp_pid)
{
	GPtrArray *children = fu_device_get_children(device);

	for (guint i = 0; i < children->len; i++) {
		FuDevice *child = g_ptr_array_index(children, i);
		if (FU_IS_HIDPP_DEVICE(child) &&
		    fu_logitech_hidpp_device_get_hidpp_pid(FU_HIDPP_DEVICE(child)) == hidpp_pid)
			return FU_HIDPP_DEVICE(g_object_ref(child));
	}

	return NULL;
}

static gchar *
fu_logitech_hidpp_runtime_bolt_query_device_name(FuLogitechHidPpRuntime *self,
						 guint8 slot,
						 GError **error)
{
	g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
	GString *dev_name = g_string_new(NULL);
	guint namelen;

	msg->report_id = HIDPP_REPORT_ID_SHORT;
	msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
	msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
	msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION;
	msg->data[0] = 0x60 | slot; /* device name */
	msg->data[1] = 1;
	msg->hidpp_version = 1;
	if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
					msg,
					error)) {
		g_prefix_error(error, "failed to retrieve the device name for slot %d: ", slot);
		return NULL;
	}
	namelen = msg->data[2];
	g_string_append_len(dev_name, (const char *)(&(msg->data[3])), namelen);

	return g_string_free(dev_name, FALSE);
}

static gboolean
fu_logitech_hidpp_runtime_bolt_update_paired_device(FuLogitechHidPpRuntimeBolt *self,
						    FuLogitechHidPpHidppMsg *msg,
						    GError **error)
{
	FuLogitechHidPpRuntime *runtime = FU_HIDPP_RUNTIME(self);
	gboolean reachable = FALSE;
	guint16 hidpp_pid;
	g_autoptr(FuLogitechHidPpDevice) child = NULL;

	if ((msg->data[0] & 0x40) == 0)
		reachable = TRUE;
	hidpp_pid = (msg->data[1] << 8) | msg->data[2];

	child = fu_logitech_hidpp_runtime_bolt_find_paired_device(FU_DEVICE(self), hidpp_pid);
	if (child != NULL) {
		g_debug("%s [%s] is reachable:%i",
			fu_device_get_name(FU_DEVICE(child)),
			fu_device_get_name(FU_DEVICE(child)),
			reachable);
		if (reachable) {
			g_autoptr(FuDeviceLocker) locker = NULL;

			/* known paired & reachable */
			fu_device_probe_invalidate(FU_DEVICE(child));
			locker = fu_device_locker_new(FU_DEVICE(child), error);
			if (locker == NULL) {
				g_prefix_error(error, "cannot rescan paired device: ");
				return FALSE;
			}
			fu_device_remove_flag(FU_DEVICE(child), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
		} else {
			GPtrArray *children = NULL;
			/* any successful 'ping' will clear this */
			fu_device_add_flag(FU_DEVICE(child), FWUPD_DEVICE_FLAG_UNREACHABLE);
			fu_device_inhibit(FU_DEVICE(child), "unreachable", "device is unreachable");
			children = fu_device_get_children(FU_DEVICE(child));
			for (guint i = 0; i < children->len; i++) {
				FuDevice *radio = g_ptr_array_index(children, i);
				fu_device_add_flag(radio, FWUPD_DEVICE_FLAG_UNREACHABLE);
				fu_device_inhibit(radio, "unreachable", "device is unreachable");
			}
		}
	} else if (reachable) {
		g_autofree gchar *name = NULL;

		/* unknown paired device, reachable state */
		name = fu_logitech_hidpp_runtime_bolt_query_device_name(runtime,
									msg->device_id,
									error);
		if (name == NULL)
			return FALSE;
		child = fu_logitech_hidpp_device_new(FU_UDEV_DEVICE(self));
		fu_device_set_name(FU_DEVICE(child), name);
		fu_logitech_hidpp_device_set_device_idx(child, msg->device_id);
		fu_logitech_hidpp_device_set_hidpp_pid(child, hidpp_pid);
		if (!fu_device_probe(FU_DEVICE(child), error))
			return FALSE;
		if (!fu_device_setup(FU_DEVICE(child), error))
			return FALSE;
		fu_device_add_child(FU_DEVICE(self), FU_DEVICE(child));
	} else {
		/* unknown paired device, unreachable state */
		g_warning("unknown paired device 0x%0x in slot %d (unreachable)",
			  hidpp_pid,
			  msg->device_id);
	}

	return TRUE;
}

void
fu_logitech_hidpp_runtime_bolt_poll_peripherals(FuDevice *device)
{
	FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
	FuLogitechHidPpRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device);

	for (guint i = 1; i <= bolt->pairing_slots; i++) {
		g_autofree gchar *name = NULL;
		g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
		g_autoptr(GError) error_local = NULL;
		guint16 hidpp_pid;

		name = fu_logitech_hidpp_runtime_bolt_query_device_name(self, i, &error_local);
		if (name == NULL) {
			g_debug("Can't query paired device name for slot %u", i);
			continue;
		}

		msg->report_id = HIDPP_REPORT_ID_SHORT;
		msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
		msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
		msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION;
		msg->data[0] = 0x50 | i; /* pairing information */
		msg->hidpp_version = 1;
		if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
						msg,
						&error_local))
			continue;
		hidpp_pid = (msg->data[2] << 8) | msg->data[3];
		if ((msg->data[1] & 0x40) == 0) {
			/* paired device is reachable */
			g_autoptr(FuLogitechHidPpDevice) child = NULL;
			child = fu_logitech_hidpp_device_new(FU_UDEV_DEVICE(device));
			fu_device_set_install_duration(FU_DEVICE(child), 270);
			fu_device_add_private_flag(FU_DEVICE(child),
						   FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO);
			fu_device_set_name(FU_DEVICE(child), name);
			fu_logitech_hidpp_device_set_device_idx(child, i);
			fu_logitech_hidpp_device_set_hidpp_pid(child, hidpp_pid);
			if (!fu_device_probe(FU_DEVICE(child), &error_local))
				continue;
			if (!fu_device_setup(FU_DEVICE(child), &error_local))
				continue;
			fu_device_add_child(device, FU_DEVICE(child));
		}
	}
}

static gboolean
fu_logitech_hidpp_runtime_bolt_process_notification(FuLogitechHidPpRuntimeBolt *self,
						    FuLogitechHidPpHidppMsg *msg)
{
	g_autoptr(GError) error_local = NULL;

	/* HID++1.0 error */
	if (!fu_logitech_hidpp_msg_is_error(msg, &error_local)) {
		g_warning("failed to get pending read: %s", error_local->message);
		return TRUE;
	}

	/* unifying receiver notification */
	if (msg->report_id == HIDPP_REPORT_ID_SHORT) {
		switch (msg->sub_id) {
		case HIDPP_SUBID_DEVICE_CONNECTION:
		case HIDPP_SUBID_DEVICE_DISCONNECTION:
		case HIDPP_SUBID_DEVICE_LOCKING_CHANGED:
			if (!fu_logitech_hidpp_runtime_bolt_update_paired_device(self,
										 msg,
										 &error_local)) {
				g_warning("failed to update paired device status: %s",
					  error_local->message);
				return FALSE;
			}
			break;
		case HIDPP_SUBID_LINK_QUALITY:
			g_debug("ignoring link quality message");
			break;
		case HIDPP_SUBID_ERROR_MSG:
			g_debug("ignoring link quality message");
			break;
		default:
			g_debug("unknown SubID %02x", msg->sub_id);
			break;
		}
	}

	return TRUE;
}

static FuLogitechHidPpHidppMsg *
fu_logitech_hidpp_runtime_bolt_find_newest_msg(GPtrArray *msgs, guint8 device_id, guint8 sub_id)
{
	for (guint i = 0; i < msgs->len; i++) {
		FuLogitechHidPpHidppMsg *msg = g_ptr_array_index(msgs, msgs->len - (i + 1));
		if (msg->device_id == device_id && msg->sub_id == sub_id)
			return msg;
	}
	return NULL;
}

static gboolean
fu_logitech_hidpp_runtime_bolt_poll(FuDevice *device, GError **error)
{
	FuLogitechHidPpRuntime *runtime = FU_HIDPP_RUNTIME(device);
	FuLogitechHidPpRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device);
	const guint timeout = 1; /* ms */
	g_autoptr(GPtrArray) msgs = g_ptr_array_new_with_free_func(g_free);

	/* open -- not a locker as we have no kernel driver */
	if (!fu_device_open(device, error))
		return FALSE;

	/* drain all the pending messages into the array */
	while (TRUE) {
		g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();
		g_autoptr(GError) error_local = NULL;
		msg->hidpp_version = 1;
		if (!fu_logitech_hidpp_receive(fu_logitech_hidpp_runtime_get_io_channel(runtime),
					       msg,
					       timeout,
					       &error_local)) {
			if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
				break;
			g_propagate_prefixed_error(error,
						   g_steal_pointer(&error_local),
						   "error polling Bolt receiver: ");
			return FALSE;
		}
		g_ptr_array_add(msgs, g_steal_pointer(&msg));
	}

	/* process messages in order, but discard any message with a newer version */
	for (guint i = 0; i < msgs->len; i++) {
		FuLogitechHidPpHidppMsg *msg = g_ptr_array_index(msgs, i);
		FuLogitechHidPpHidppMsg *msg_newest;

		/* find the newest message with the matching device and sub-IDs */
		msg_newest = fu_logitech_hidpp_runtime_bolt_find_newest_msg(msgs,
									    msg->device_id,
									    msg->sub_id);
		if (msg != msg_newest) {
			g_debug("ignoring duplicate message device-id:%02x [%s] sub-id:%02x [%s]",
				msg->device_id,
				fu_logitech_hidpp_msg_dev_id_to_string(msg),
				msg->sub_id,
				fu_logitech_hidpp_msg_sub_id_to_string(msg));
			continue;
		}
		fu_logitech_hidpp_runtime_bolt_process_notification(self, msg);
	}

	return TRUE;
}

static gboolean
fu_logitech_hidpp_runtime_bolt_setup_internal(FuDevice *device, GError **error)
{
	FuContext *ctx = fu_device_get_context(device);
	FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device);
	FuLogitechHidPpRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device);
	g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new();

	msg->report_id = HIDPP_REPORT_ID_SHORT;
	msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
	msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
	msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION;
	msg->data[0] = 0x02; /* FW Version (contains the number of pairing slots) */
	msg->hidpp_version = 1;
	if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
					msg,
					error)) {
		g_prefix_error(error, "failed to fetch the number of pairing slots: ");
		return FALSE;
	}
	bolt->pairing_slots = msg->data[8];

	/*
	 * TODO: Iterate only over the first three entity indexes for
	 * now.
	 */
	for (guint i = 0; i < 3; i++) {
		g_autofree gchar *version = NULL;
		g_autoptr(FuLogitechHidPpRadio) radio = NULL;
		g_autofree gchar *instance_id = NULL;
		g_autoptr(GString) radio_version = NULL;

		msg->report_id = HIDPP_REPORT_ID_SHORT;
		msg->device_id = HIDPP_DEVICE_IDX_RECEIVER;
		msg->sub_id = HIDPP_SUBID_GET_LONG_REGISTER;
		msg->function_id = BOLT_REGISTER_RECEIVER_FW_INFORMATION;
		msg->data[0] = i;
		msg->hidpp_version = 1;
		if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self),
						msg,
						error)) {
			g_prefix_error(error, "failed to read device config: ");
			return FALSE;
		}
		switch (msg->data[0]) {
		case 0:
			/* main application */
			version = fu_logitech_hidpp_format_version("MPR",
								   msg->data[1],
								   msg->data[2],
								   (guint16)msg->data[3] << 8 |
								       msg->data[4]);
			fu_device_set_version(device, version);
			break;
		case 1:
			/* bootloader */
			version = fu_logitech_hidpp_format_version("BOT",
								   msg->data[1],
								   msg->data[2],
								   (guint16)msg->data[3] << 8 |
								       msg->data[4]);
			fu_device_set_version_bootloader(device, version);
			break;
		case 5:
			/* SoftDevice */
			radio_version = g_string_new(NULL);
			radio = fu_logitech_hidpp_radio_new(ctx, i);
			fu_device_set_physical_id(FU_DEVICE(radio),
						  fu_device_get_physical_id(device));
			fu_device_set_logical_id(FU_DEVICE(radio), "Receiver_SoftDevice");

			instance_id =
			    g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&ENT_05",
					    fu_udev_device_get_vendor(FU_UDEV_DEVICE(device)),
					    fu_udev_device_get_model(FU_UDEV_DEVICE(device)));
			fu_device_add_guid(FU_DEVICE(radio), instance_id);
			g_string_append_printf(radio_version,
					       "0x%.4x",
					       (guint16)(msg->data[3] << 8 | msg->data[4]));
			fu_device_set_version(FU_DEVICE(radio), radio_version->str);
			fu_device_add_child(device, FU_DEVICE(radio));
			break;
		default:
			break;
		}
	}

	/* enable HID++ notifications */
	if (!fu_logitech_hidpp_runtime_enable_notifications(self, error)) {
		g_prefix_error(error, "failed to enable notifications: ");
		return FALSE;
	}
	fu_logitech_hidpp_runtime_bolt_poll_peripherals(device);

	/* success */
	return TRUE;
}

static gboolean
fu_logitech_hidpp_runtime_bolt_setup(FuDevice *device, GError **error)
{
	g_autoptr(GError) error_local = NULL;
	for (guint i = 0; i < 5; i++) {
		g_clear_error(&error_local);
		/* HID++1.0 devices have to sleep to allow Solaar to talk to
		 * the device first -- we can't use the SwID as this is a
		 * HID++2.0 feature */
		g_usleep(200 * 1000);
		if (fu_logitech_hidpp_runtime_bolt_setup_internal(device, &error_local))
			return TRUE;
		if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) {
			g_propagate_error(error, g_steal_pointer(&error_local));
			return FALSE;
		}
	}
	g_propagate_error(error, g_steal_pointer(&error_local));
	return FALSE;
}

static void
fu_logitech_hidpp_runtime_bolt_class_init(FuLogitechHidPpRuntimeBoltClass *klass)
{
	FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);

	klass_device->detach = fu_logitech_hidpp_runtime_bolt_detach;
	klass_device->setup = fu_logitech_hidpp_runtime_bolt_setup;
	klass_device->poll = fu_logitech_hidpp_runtime_bolt_poll;
	klass_device->to_string = fu_logitech_hidpp_runtime_bolt_to_string;
}

static void
fu_logitech_hidpp_runtime_bolt_init(FuLogitechHidPpRuntimeBolt *self)
{
	fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG);
	fu_device_set_name(FU_DEVICE(self), "Bolt Receiver");
	fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned");
}

{'fraction_non_alphanumeric': 0.11149660936760764, 'fraction_numerical': 0.008011354675918625, 'mean_word_length': 5.1315026107135955, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 179, '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': '3628854', 'n_tokens_mistral': 14150, 'n_tokens_neox': 12627, 'n_words': 2013}