// SPDX-License-Identifier: LGPL-2.1-or-later
#include "internal.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>

/**
 * HinawaFwNode:
 * An event listener for node in IEEE 1394 bus.
 *
 * [class@FwNode] listens to any events for an associated node in IEEE 1394 bus. Additionally,
 * it provides some methods to retrieve fundamental information about the bus.
 *
 * Since: 1.4
 */

// 256 comes from an actual implementation in kernel land. Read
// 'drivers/firewire/core-device.c'. This value is calculated by a range for
// configuration ROM in ISO/IEC 13213 (IEEE 1212).
#define MAX_CONFIG_ROM_SIZE	256
#define MAX_CONFIG_ROM_LENGTH	(MAX_CONFIG_ROM_SIZE * 4)

typedef struct {
	int fd;

	GMutex mutex;
	guint8 config_rom[MAX_CONFIG_ROM_LENGTH];
	gsize config_rom_length;
	struct fw_cdev_event_bus_reset generation;
	guint32 card_id;

	GList *transactions;
	GMutex transactions_mutex;
} HinawaFwNodePrivate;
G_DEFINE_TYPE_WITH_PRIVATE(HinawaFwNode, hinawa_fw_node, G_TYPE_OBJECT)

/**
 * hinawa_fw_node_error_quark:
 *
 * Return the [alias@GLib.Quark] for [struct@GLib.Error] with [error@FwNodeError] domain.
 *
 * Since: 2.1
 *
 * Returns: A [alias@GLib.Quark].
 */
G_DEFINE_QUARK(hinawa-fw-node-error-quark, hinawa_fw_node_error)

static const char *const err_msgs[] = {
	[HINAWA_FW_NODE_ERROR_DISCONNECTED] = "The associated node is not available for communication",
	[HINAWA_FW_NODE_ERROR_OPENED] = "The instance is already associated to node",
	[HINAWA_FW_NODE_ERROR_NOT_OPENED] = "The instance is not associated to node",
};

#define generate_local_error(error, code) \
	g_set_error_literal(error, HINAWA_FW_NODE_ERROR, code, err_msgs[code])

#define generate_file_error(error, code, format, arg) \
	g_set_error(error, G_FILE_ERROR, code, format, arg)

#define generate_syscall_error(error, errno, format, arg)				\
	g_set_error(error, HINAWA_FW_NODE_ERROR, HINAWA_FW_NODE_ERROR_FAILED,	\
		    format " %d(%s)", arg, errno, strerror(errno))

typedef struct {
	GSource src;
	HinawaFwNode *self;
	gpointer tag;
	size_t len;
	void *buf;
} FwNodeSource;

enum fw_node_prop_type {
	FW_NODE_PROP_TYPE_NODE_ID = 1,
	FW_NODE_PROP_TYPE_LOCAL_NODE_ID,
	FW_NODE_PROP_TYPE_BUS_MANAGER_NODE_ID,
	FW_NODE_PROP_TYPE_IR_MANAGER_NODE_ID,
	FW_NODE_PROP_TYPE_ROOT_NODE_ID,
	FW_NODE_PROP_TYPE_GENERATION,
	FW_NODE_PROP_TYPE_CARD_ID,
	FW_NODE_PROP_TYPE_COUNT,
};
static GParamSpec *fw_node_props[FW_NODE_PROP_TYPE_COUNT] = { NULL, };

// This object has two signals.
enum fw_node_sig_type {
	FW_NODE_SIG_TYPE_BUS_UPDATE = 0,
	FW_NODE_SIG_TYPE_DISCONNECTED,
	FW_NODE_SIG_TYPE_COUNT,
};
static guint fw_node_sigs[FW_NODE_SIG_TYPE_COUNT] = { 0 };

static void fw_node_finalize(GObject *obj)
{
	HinawaFwNode *self = HINAWA_FW_NODE(obj);
	HinawaFwNodePrivate *priv = hinawa_fw_node_get_instance_private(self);

	if (priv->fd >= 0)
		close(priv->fd);

	g_list_free(priv->transactions);

	G_OBJECT_CLASS(hinawa_fw_node_parent_class)->finalize(obj);
}

static void fw_node_get_property(GObject *obj, guint id,
				 GValue *val, GParamSpec *spec)
{
	HinawaFwNode *self = HINAWA_FW_NODE(obj);
	HinawaFwNodePrivate *priv = hinawa_fw_node_get_instance_private(self);

	g_mutex_lock(&priv->mutex);

	switch (id) {
	case FW_NODE_PROP_TYPE_NODE_ID:
		g_value_set_uint(val, priv->generation.node_id);
		break;
	case FW_NODE_PROP_TYPE_LOCAL_NODE_ID:
		g_value_set_uint(val, priv->generation.local_node_id);
		break;
	case FW_NODE_PROP_TYPE_BUS_MANAGER_NODE_ID:
		g_value_set_uint(val, priv->generation.bm_node_id);
		break;
	case FW_NODE_PROP_TYPE_IR_MANAGER_NODE_ID:
		g_value_set_uint(val, priv->generation.irm_node_id);
		break;
	case FW_NODE_PROP_TYPE_ROOT_NODE_ID:
		g_value_set_uint(val, priv->generation.root_node_id);
		break;
	case FW_NODE_PROP_TYPE_GENERATION:
		g_value_set_uint(val, priv->generation.generation);
		break;
	case FW_NODE_PROP_TYPE_CARD_ID:
		g_value_set_uint(val, priv->card_id);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec);
		break;
	}

	g_mutex_unlock(&priv->mutex);
}

static void hinawa_fw_node_class_init(HinawaFwNodeClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

	gobject_class->finalize = fw_node_finalize;
	gobject_class->get_property = fw_node_get_property;

	/**
	 * HinawaFwNode:node-id:
	 *
	 * Node ID of node associated to instance of object at current generation of bus topology.
	 * This parameter is effective after the association.
	 *
	 * Since: 1.4
	 */
	fw_node_props[FW_NODE_PROP_TYPE_NODE_ID] =
		g_param_spec_uint("node-id", "node-id",
				  "Node ID of node associated to instance of object at current "
				  "generation of bus topology. This parameter is effective after "
				  "the association.",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	/**
	 * HinawaFwNode:local-node-id:
	 *
	 * Node ID of node which application uses to communicate to node associated to instance of
	 * object at current generation of bus topology. In general, it is for 1394 OHCI hardware.
	 *
	 * Since: 1.4
	 */
	fw_node_props[FW_NODE_PROP_TYPE_LOCAL_NODE_ID] =
		g_param_spec_uint("local-node-id", "local-node-id",
				  "Node ID of node which application uses to communicate to node "
				  "associated to instance of object at current generation of bus "
				  "topology. In general, it is for 1394 OHCI hardware.",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	/**
	 * HinawaFwNode:bus-manager-node-id:
	 *
	 * Node ID of node which plays role of bus manager at current generation of bus topology.
	 *
	 * Since: 1.4
	 */
	fw_node_props[FW_NODE_PROP_TYPE_BUS_MANAGER_NODE_ID] =
		g_param_spec_uint("bus-manager-node-id", "bus-manager-node-id",
				  "Node ID of node which plays role of bus manager at current "
				  "generation of bus topology.",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	/**
	 * HinawaFwNode:ir-manager-node-id:
	 *
	 * Node ID of node which plays role of isochronous resource manager at current generation
	 * of bus topology.
	 *
	 * Since: 1.4
	 */
	fw_node_props[FW_NODE_PROP_TYPE_IR_MANAGER_NODE_ID] =
		g_param_spec_uint("ir-manager-node-id", "ir-manager-node-id",
				  "Node ID of node which plays role of isochronous resource "
				  "manager at current generation of bus topology.",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	/**
	 * HinawaFwNode:root-node-id:
	 *
	 * Node ID of root node in bus topology at current generation of bus topology.
	 *
	 * Since: 1.4
	 */
	fw_node_props[FW_NODE_PROP_TYPE_ROOT_NODE_ID] =
		g_param_spec_uint("root-node-id", "root-node-id",
				  "Node ID of root node in bus topology at current generation of "
				  "the bus topology.",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	/**
	 * HinawaFwNode:generation:
	 *
	 * Current generation of bus topology.
	 *
	 * Since: 1.4
	 */
	fw_node_props[FW_NODE_PROP_TYPE_GENERATION] =
		g_param_spec_uint("generation", "generation",
				  "Current generation of bus topology",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	/**
	 * HinawaFwNode:card-id:
	 *
	 * The numeric index for 1394 OHCI hardware used for the communication with the node. The
	 * value is stable against bus generation.
	 *
	 * Since: 4.0
	 */
	fw_node_props[FW_NODE_PROP_TYPE_CARD_ID] =
		g_param_spec_uint("card-id", "card-id",
				  "The numeric index for 1394 OHCI hardware used for the "
				  "communication with the node",
				  0, G_MAXUINT32, 0,
				  G_PARAM_READABLE);

	g_object_class_install_properties(gobject_class,
					  FW_NODE_PROP_TYPE_COUNT,
					  fw_node_props);

	/**
	 * HinawaFwNode::bus-update:
	 * @self: A [class@FwNode].
	 *
	 * Emitted when IEEE 1394 bus is updated. Handlers can read current generation in the bus
	 * via [property@FwNode:generation] property.
	 *
	 * Since: 1.4
	 */
	fw_node_sigs[FW_NODE_SIG_TYPE_BUS_UPDATE] =
		g_signal_new("bus-update",
			     G_OBJECT_CLASS_TYPE(klass),
			     G_SIGNAL_RUN_LAST,
			     G_STRUCT_OFFSET(HinawaFwNodeClass, bus_update),
			     NULL, NULL,
			     g_cclosure_marshal_VOID__VOID,
			     G_TYPE_NONE, 0, G_TYPE_NONE);

	/**
	 * HinawaFwNode::disconnected:
	 * @self: A [class@FwNode].
	 *
	 * Emitted when the node is not available anymore in Linux system. It's preferable to call
	 * [method@GObject.Object.unref] immediately to release file descriptor.
	 *
	 * Since: 1.4
	 */
	fw_node_sigs[FW_NODE_SIG_TYPE_DISCONNECTED] =
		g_signal_new("disconnected",
			     G_OBJECT_CLASS_TYPE(klass),
			     G_SIGNAL_RUN_LAST,
			     G_STRUCT_OFFSET(HinawaFwNodeClass, disconnected),
			     NULL, NULL,
			     g_cclosure_marshal_VOID__VOID,
			     G_TYPE_NONE, 0, G_TYPE_NONE);
}

static void hinawa_fw_node_init(HinawaFwNode *self)
{
	HinawaFwNodePrivate *priv = hinawa_fw_node_get_instance_private(self);

	priv->fd = -1;
	g_mutex_init(&priv->mutex);
	g_mutex_init(&priv->transactions_mutex);
}

/**
 * hinawa_fw_node_new:
 *
 * Instantiate [class@FwNode] object and return the instance.
 *
 * Returns: an instance of [class@FwNode].
 * Since: 1.4
 */
HinawaFwNode *hinawa_fw_node_new(void)
{
	return g_object_new(HINAWA_TYPE_FW_NODE, NULL);
}

static int update_info(HinawaFwNode *self)
{
	HinawaFwNodePrivate *priv = hinawa_fw_node_get_instance_private(self);
	struct fw_cdev_get_info info = {0};
	guint32 *rom;
	unsigned int quads;
	int i;

	// The interface version 4 is used for:
	//    - struct fw_cdev_allocate.region_end
	// The interface version 6 is used for:
	//    - struct fw_cdev_event_request3
	//    - struct fw_cdev_event_response2
	info.version = 6;
	info.rom = (__u64)priv->config_rom;
	info.rom_length = MAX_CONFIG_ROM_LENGTH;
	info.bus_reset = (__u64)&priv->generation;
	info.bus_reset_closure = (__u64)self;
	if (ioctl(priv->fd, FW_CDEV_IOC_GET_INFO, &info) < 0)
		return errno;

	// Linux FireWire subsystem caches the content of configuration ROM by host-endian.
	rom = (guint32 *)priv->config_rom;
	quads = (info.rom_length + 3) / 4;
	for (i = 0; i < quads; ++i)
		rom[i] = GUINT32_TO_BE(rom[i]);
	priv->config_rom_length = info.rom_length;

	priv->card_id = info.card;

	return 0;
}

/**
 * hinawa_fw_node_open:
 * @self: A [class@FwNode]
 * @path: A path to Linux FireWire character device
 * @open_flag: The flag of `open(2)` system call. `O_RDONLY` is fulfilled internally.
 * @error: A [struct@GLib.Error]. Error can be generated with two domains; [error@GLib.FileError]
 *	   and [error@FwNodeError].
 *
 * Open Linux FireWire character device to operate node in IEEE 1394 bus.
 *
 * Returns: TRUE if the overall operation finishes successfully, otherwise FALSE.
 *
 * Since: 4.0
 */
gboolean hinawa_fw_node_open(HinawaFwNode *self, const gchar *path, gint open_flag, GError **error)
{
	HinawaFwNodePrivate *priv;
	int err;

	g_return_val_if_fail(HINAWA_IS_FW_NODE(self), FALSE);
	g_return_val_if_fail(path != NULL && strlen(path) > 0, FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	priv = hinawa_fw_node_get_instance_private(self);
	if (priv->fd >= 0) {
		generate_local_error(error, HINAWA_FW_NODE_ERROR_OPENED);
		return FALSE;
	}

	open_flag |= O_RDONLY;
	priv->fd = open(path, open_flag);
	if (priv->fd < 0) {
		if (errno == ENODEV) {
			generate_local_error(error, HINAWA_FW_NODE_ERROR_DISCONNECTED);
		} else {
			GFileError code = g_file_error_from_errno(errno);
			if (code != G_FILE_ERROR_FAILED)
				generate_file_error(error, code, "open(%s)", path);
			else
				generate_syscall_error(error, errno, "open(%s)", path);
		}
		return FALSE;
	}

	g_mutex_lock(&priv->mutex);
	err = update_info(self);
	g_mutex_unlock(&priv->mutex);

	if (err > 0) {
		if (err == ENODEV)
			generate_local_error(error, HINAWA_FW_NODE_ERROR_DISCONNECTED);
		else
			generate_syscall_error(error, errno, "ioctl(%s)", "FW_CDEV_IOC_GET_INFO");
		close(priv->fd);
		priv->fd = -1;

		return FALSE;
	}

	return TRUE;
}

/**
 * hinawa_fw_node_get_config_rom:
 * @self: A [class@FwNode]
 * @image: (array length=length)(out)(transfer none): The content of configuration ROM.
 * @length: (out): The number of bytes consists of the configuration ROM.
 * @error: A [struct@GLib.Error].
 *
 * Get cached content of configuration ROM aligned to big-endian.
 *
 * Returns: TRUE if the overall operation finishes successfully, otherwise FALSE.
 *
 * Since: 4.0
 */
gboolean hinawa_fw_node_get_config_rom(HinawaFwNode *self, const guint8 **image, gsize *length,
				       GError **error)
{
	HinawaFwNodePrivate *priv;

	g_return_val_if_fail(HINAWA_IS_FW_NODE(self), FALSE);
	g_return_val_if_fail(image != NULL, FALSE);
	g_return_val_if_fail(length != NULL, FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	priv = hinawa_fw_node_get_instance_private(self);
	if (priv->fd < 0) {
		generate_local_error(error, HINAWA_FW_NODE_ERROR_NOT_OPENED);
		return FALSE;
	}

	g_mutex_lock(&priv->mutex);

	*image = priv->config_rom;
	*length = priv->config_rom_length;

	g_mutex_unlock(&priv->mutex);

	return TRUE;
}

/**
 * hinawa_fw_node_read_cycle_time:
 * @self: A [class@FwNode]
 * @clock_id: The numeric ID of clock source for the reference timestamp. One of CLOCK_REALTIME(0),
 *	      CLOCK_MONOTONIC(1), and CLOCK_MONOTONIC_RAW(4) is available in UAPI of Linux kernel.
 * @cycle_time: (inout): A [struct@CycleTime].
 * @error: A [struct@GLib.Error].
 *
 * Read current value of CYCLE_TIME register in 1394 OHCI hardware dedicated to communicate with
 * the associated node in IEEE 1394 bus.
 *
 * Returns: TRUE if the overall operation finishes successfully, otherwise FALSE.
 *
 * Since: 4.0
 */
gboolean hinawa_fw_node_read_cycle_time(HinawaFwNode *self, gint clock_id,
					HinawaCycleTime **cycle_time, GError **error)
{
	int err;

	g_return_val_if_fail(cycle_time != NULL, FALSE);

	(*cycle_time)->clk_id = clock_id;
	err = hinawa_fw_node_ioctl(self, FW_CDEV_IOC_GET_CYCLE_TIMER2, *cycle_time, error);
	if (err < 0 && *error == NULL)
		generate_syscall_error(error, errno, "ioctl(%s)", "FW_CDEV_IOC_GET_CYCLE_TIMER2");

	return err == 0;
}

static void handle_update(HinawaFwNode *self)
{
	HinawaFwNodePrivate *priv;

	g_return_if_fail(HINAWA_IS_FW_NODE(self));
	priv = hinawa_fw_node_get_instance_private(self);

	g_mutex_lock(&priv->mutex);
	update_info(self);
	g_mutex_unlock(&priv->mutex);

	g_signal_emit(self, fw_node_sigs[FW_NODE_SIG_TYPE_BUS_UPDATE], 0, NULL);
}

static gboolean check_src(GSource *gsrc)
{
	FwNodeSource *src = (FwNodeSource *)gsrc;
	GIOCondition condition;

	// Don't go to dispatch if nothing available. As an error, return
	// TRUE for POLLERR to call .dispatch for internal destruction.
	condition = g_source_query_unix_fd(gsrc, src->tag);
	return !!(condition & (G_IO_IN | G_IO_ERR));
}

static gboolean dispatch_src(GSource *gsrc, GSourceFunc cb, gpointer user_data)
{
	FwNodeSource *src = (FwNodeSource *)gsrc;
	HinawaFwNodePrivate *priv;
	GIOCondition condition;
	const union fw_cdev_event *event;
	gpointer instance;
	__u32 event_type;
	ssize_t len;

	priv = hinawa_fw_node_get_instance_private(src->self);
	if (priv->fd < 0)
		return G_SOURCE_REMOVE;

	condition = g_source_query_unix_fd(gsrc, src->tag);
	if (condition & G_IO_ERR) {
		g_signal_emit(src->self,
			fw_node_sigs[FW_NODE_SIG_TYPE_DISCONNECTED], 0);
		return G_SOURCE_REMOVE;
	}

	len = read(priv->fd, src->buf, src->len);
	if (len < 0) {
		if (errno == EAGAIN)
			return G_SOURCE_CONTINUE;

		return G_SOURCE_REMOVE;
	}

	event = (const union fw_cdev_event *)src->buf;
	instance = (gpointer)event->common.closure;
	event_type = event->common.type;

	if (HINAWA_IS_FW_NODE(instance) && event_type == FW_CDEV_EVENT_BUS_RESET) {
		handle_update(src->self);
	} else if (HINAWA_IS_FW_RESP(instance)) {
		HinawaFwResp *resp = HINAWA_FW_RESP(instance);

		switch (event_type) {
		case FW_CDEV_EVENT_REQUEST:
			hinawa_fw_resp_handle_request(resp, &event->request);
			break;
		case FW_CDEV_EVENT_REQUEST2:
			hinawa_fw_resp_handle_request2(resp, &event->request2);
			break;
		case FW_CDEV_EVENT_REQUEST3:
			hinawa_fw_resp_handle_request3(resp, &event->request3);
			break;
		default:
			break;
		}
	} else if (HINAWA_IS_FW_REQ(instance)) {
		HinawaFwReq *req = HINAWA_FW_REQ(instance);
		GList *entry;

		// Don't process request invalidated in advance.
		g_mutex_lock(&priv->transactions_mutex);
		entry = g_list_find(priv->transactions, req);
		if (entry) {
			priv->transactions = g_list_delete_link(priv->transactions, entry);

			switch (event_type) {
			case FW_CDEV_EVENT_RESPONSE:
				hinawa_fw_req_handle_response(req, &event->response);
				break;
			case FW_CDEV_EVENT_RESPONSE2:
				hinawa_fw_req_handle_response2(req, &event->response2);
				break;
			default:
				break;
			}
		}
		g_mutex_unlock(&priv->transactions_mutex);
	}

	// Just be sure to continue to process this source.
	return G_SOURCE_CONTINUE;
}

static void finalize_src(GSource *gsrc)
{
	FwNodeSource *src = (FwNodeSource *)gsrc;

	g_free(src->buf);
}

/**
 * hinawa_fw_node_create_source:
 * @self: A [class@FwNode].
 * @gsrc: (out): A [struct@GLib.Source].
 * @error: A [struct@GLib.Error]. Error can be generated with domain of [error@FwNodeError].
 *
 * Create [struct@GLib.Source] for [struct@GLib.MainContext] to dispatch events for the node on
 * IEEE 1394 bus.
 *
 * Returns: TRUE if the overall operation finishes successfully, otherwise FALSE.
 *
 * Since: 4.0
 */
gboolean hinawa_fw_node_create_source(HinawaFwNode *self, GSource **gsrc, GError **error)
{
        static GSourceFuncs funcs = {
                .check          = check_src,
                .dispatch       = dispatch_src,
                .finalize       = finalize_src,
        };
	HinawaFwNodePrivate *priv;
	FwNodeSource *src;

	g_return_val_if_fail(HINAWA_IS_FW_NODE(self), FALSE);
	g_return_val_if_fail(gsrc != NULL, FALSE);
	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	priv = hinawa_fw_node_get_instance_private(self);
	if (priv->fd < 0) {
		generate_local_error(error, HINAWA_FW_NODE_ERROR_NOT_OPENED);
		return FALSE;
	}

        *gsrc = g_source_new(&funcs, sizeof(FwNodeSource));
	src = (FwNodeSource *)(*gsrc);

        g_source_set_name(*gsrc, "HinawaFwNode");

        // MEMO: allocate one page because we cannot assume the size of
        // transaction frame.
        src->len = sysconf(_SC_PAGESIZE);
        src->buf = g_malloc0(src->len);

	src->self = self;
	src->tag = g_source_add_unix_fd(*gsrc, priv->fd, G_IO_IN);

	return TRUE;
}

// Internal use only.
int hinawa_fw_node_ioctl(HinawaFwNode *self, unsigned long req, void *args, GError **error)
{
	HinawaFwNodePrivate *priv;

	g_return_val_if_fail(HINAWA_IS_FW_NODE(self), ENXIO);
	g_return_val_if_fail(error != NULL, EINVAL);

	priv = hinawa_fw_node_get_instance_private(self);
	if (priv->fd < 0) {
		generate_local_error(error, HINAWA_FW_NODE_ERROR_NOT_OPENED);
		return ENXIO;
	}

	// To invalidate the transaction in a case of timeout.
	if (req == FW_CDEV_IOC_SEND_REQUEST) {
		struct fw_cdev_send_request *data = args;
		HinawaFwReq *req = (HinawaFwReq *)data->closure;
		priv->transactions = g_list_prepend(priv->transactions, req);
	}

	if (ioctl(priv->fd, req, args) < 0) {
		if (errno == ENODEV)
			generate_local_error(error, HINAWA_FW_NODE_ERROR_DISCONNECTED);
		return errno;
	}

	return 0;
}

void hinawa_fw_node_invalidate_transaction(HinawaFwNode *self, HinawaFwReq *req)
{
	HinawaFwNodePrivate *priv;
	GList *entry;

	g_return_if_fail(HINAWA_IS_FW_NODE(self));
	priv = hinawa_fw_node_get_instance_private(self);

	g_mutex_lock(&priv->transactions_mutex);
	entry = g_list_find(priv->transactions, req);
	if (entry) {
		priv->transactions =
				g_list_delete_link(priv->transactions, entry);
	}
	g_mutex_unlock(&priv->transactions_mutex);
}
