--- a/discover/device-handler.c
+++ b/discover/device-handler.c
@@ -15,6 +15,7 @@
 
 #include "device-handler.h"
 #include "discover-server.h"
+#include "event.h"
 #include "parser.h"
 #include "udev.h"
 #include "paths.h"
@@ -63,7 +64,7 @@ static void device_handler_remove(struct
 		if (handler->devices[i] == device)
 			break;
 
-	if (i < handler->n_devices) {
+	if (i == handler->n_devices) {
 		assert(0 && "unknown device");
 		return;
 	}
@@ -91,7 +92,7 @@ static struct device *device_handler_fin
 			&& streq(handler->devices[i]->id, id))
 			return handler->devices[i];
 
-	assert(0 && "unknown device");
+	pb_log("%s: unknown device: %s\n", __func__, id);
 	return NULL;
 }
 
@@ -141,7 +142,7 @@ static void setup_device_links(struct di
 		char *enc, *dir, *path;
 		const char *value;
 
-		value = udev_event_param(ctx->event, link->env);
+		value = event_get_param(ctx->event, link->env);
 		if (!value || !*value)
 			continue;
 
@@ -266,8 +267,8 @@ static int destroy_context(void *arg)
 	return 0;
 }
 
-static int handle_add_event(struct device_handler *handler,
-		struct udev_event *event)
+static int handle_add_udev_event(struct device_handler *handler,
+		struct event *event)
 {
 	struct discover_context *ctx;
 	const char *devname;
@@ -282,7 +283,7 @@ static int handle_add_event(struct devic
 
 	ctx->id = talloc_strdup(ctx, event->device);
 
-	devname = udev_event_param(ctx->event, "DEVNAME");
+	devname = event_get_param(ctx->event, "DEVNAME");
 	if (!devname) {
 		pb_log("no devname for %s?\n", event->device);
 		return 0;
@@ -315,8 +316,8 @@ static int handle_add_event(struct devic
 	return 0;
 }
 
-static int handle_remove_event(struct device_handler *handler,
-		struct udev_event *event)
+static int handle_remove_udev_event(struct device_handler *handler,
+		struct event *event)
 {
 	struct discover_context *ctx;
 
@@ -335,17 +336,29 @@ static int handle_remove_event(struct de
 }
 
 int device_handler_event(struct device_handler *handler,
-		struct udev_event *event)
+		struct event *event)
 {
-	int rc;
+	int rc = 0;
 
-	switch (event->action) {
-	case UDEV_ACTION_ADD:
-		rc = handle_add_event(handler, event);
+	switch (event->type) {
+	case EVENT_TYPE_UDEV:
+		switch (event->action) {
+		case EVENT_ACTION_ADD:
+			rc = handle_add_udev_event(handler, event);
+			break;
+		case EVENT_ACTION_REMOVE:
+			rc = handle_remove_udev_event(handler, event);
+			break;
+		default:
+			pb_log("%s unknown action: %d\n", __func__,
+				event->action);
+			break;
+		}
 		break;
-
-	case UDEV_ACTION_REMOVE:
-		rc = handle_remove_event(handler, event);
+	case EVENT_TYPE_USER:
+		break;
+	default:
+		pb_log("%s unknown type: %d\n", __func__, event->type);
 		break;
 	}
 
--- a/discover/device-handler.h
+++ b/discover/device-handler.h
@@ -5,14 +5,14 @@
 
 struct device_handler;
 struct discover_server;
-struct udev_event;
+struct event;
 struct device;
 
 struct discover_context {
 	char *id;
 	char *device_path;
 	char *mount_path;
-	struct udev_event *event;
+	struct event *event;
 	struct device *device;
 	char **links;
 	int n_links;
@@ -28,7 +28,6 @@ int device_handler_get_device_count(cons
 const struct device *device_handler_get_device(
 	const struct device_handler *handler, unsigned int index);
 
-int device_handler_event(struct device_handler *handler,
-		struct udev_event *event);
+int device_handler_event(struct device_handler *handler, struct event *event);
 
 #endif /* _DEVICE_HANDLER_H */
--- /dev/null
+++ b/discover/event.c
@@ -0,0 +1,134 @@
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <log/log.h>
+#include <talloc/talloc.h>
+
+#include "event.h"
+
+#define streq(a, b) (!strcasecmp((a), (b)))
+
+/**
+ * event_parse_ad_header - Parse an <action>@<device> event header.
+ *
+ * The buffer is modified in place.
+ * Returns zero on success.
+ */
+
+static int event_parse_ad_header(char *buf, int len, enum event_action *action,
+	char **device)
+{
+	char *sep;
+
+	*action = 0;
+	*device = NULL;
+
+	/* we should see an <action>@<device>\0 at the head of the buffer */
+	sep = strchr(buf, '@');
+	if (!sep) {
+		pb_log("%s: bad header: %s\n", __func__, buf);
+		return -1;
+	}
+
+	/* terminate the action string */
+	*sep = '\0';
+	len -= sep - buf + 1;
+
+	if (streq(buf, "add"))
+		*action = EVENT_ACTION_ADD;
+	else if (streq(buf, "remove"))
+		*action = EVENT_ACTION_REMOVE;
+	else {
+		pb_log("%s: unknown action: %s\n", __func__, buf);
+		return -1;
+	}
+
+	if (!*(sep + 1)) {
+		pb_log("%s: bad device: %s\n", __func__, buf);
+		return -1;
+	}
+
+	*device = sep + 1;
+	return 0;
+}
+
+/**
+ * event_parse_params - Parse a <name>=<value> buffer.
+ *
+ * The buffer is not modified.
+ */
+
+static void event_parse_params(struct event *event, const char *buf, int len)
+{
+	int param_len, name_len, value_len;
+	struct param *param;
+	char *sep;
+
+	for (; len > 0; len -= param_len + 1, buf += param_len + 1) {
+
+		/* find the length of the whole parameter */
+		param_len = strnlen(buf, len);
+		if (!param_len) {
+			/* multiple NULs? skip over */
+			param_len = 1;
+			continue;
+		}
+
+		/* find the separator */
+		sep = memchr(buf, '=', param_len);
+		if (!sep)
+			continue;
+
+		name_len = sep - buf;
+		value_len = param_len - name_len - 1;
+
+		/* update the params array */
+		event->params = talloc_realloc(event, event->params,
+					struct param, ++event->n_params);
+		param = &event->params[event->n_params - 1];
+
+		param->name = talloc_strndup(event, buf, name_len);
+		param->value = talloc_strndup(event, sep + 1, value_len);
+	}
+}
+
+int event_parse_ad_message(struct event *event, char *buf, int len)
+{
+	int result;
+	char *device;
+	enum event_action action;
+	int device_len;
+
+	result = event_parse_ad_header(buf, len, &action, &device);
+
+	if (result)
+		return -1;
+
+	device_len = strlen(device);
+
+	/* now we have an action and a device, we can construct the event */
+	event->action = action;
+	event->device = talloc_strndup(event, device, device_len);
+	event->n_params = 0;
+	event->params = NULL;
+
+	len -= device_len + 1;
+	event_parse_params(event, device + device_len + 1, len);
+
+	return 0;
+}
+
+const char *event_get_param(const struct event *event, const char *name)
+{
+	int i;
+
+	for (i = 0; i < event->n_params; i++)
+		if (!strcasecmp(event->params[i].name, name))
+			return event->params[i].value;
+
+	return NULL;
+}
--- /dev/null
+++ b/discover/event.h
@@ -0,0 +1,29 @@
+#ifndef _PB_EVENT_H
+#define _PB_EVENT_H
+
+enum event_type {
+	EVENT_TYPE_UDEV = 10,
+	EVENT_TYPE_USER,
+};
+
+enum event_action {
+	EVENT_ACTION_ADD = 20,
+	EVENT_ACTION_REMOVE,
+};
+
+struct event {
+	enum event_type type;
+	enum event_action action;
+	char *device;
+
+	struct param {
+		char *name;
+		char *value;
+	} *params;
+	int n_params;
+};
+
+int event_parse_ad_message(struct event *event, char *buf, int len);
+const char *event_get_param(const struct event *event, const char *name);
+
+#endif /* _PB_EVENT_H */
--- a/discover/parser-utils.c
+++ b/discover/parser-utils.c
@@ -5,6 +5,7 @@
 #include <talloc/talloc.h>
 
 #include "pb-protocol/pb-protocol.h"
+#include "event.h"
 #include "udev.h"
 #include "device-handler.h"
 #include "parser-utils.h"
@@ -42,13 +43,13 @@ const char *generic_icon_file(enum gener
 
 enum generic_icon_type guess_device_type(struct discover_context *ctx)
 {
-	struct udev_event *event;
+	struct event *event;
 	const char *type, *bus;
 
 	event = ctx->event;
 
-	type = udev_event_param(event, "ID_TYPE");
-	bus = udev_event_param(event, "ID_BUS");
+	type = event_get_param(event, "ID_TYPE");
+	bus = event_get_param(event, "ID_BUS");
 
 	if (type && streq(type, "cd"))
 		return ICON_TYPE_OPTICAL;
--- a/discover/udev.c
+++ b/discover/udev.c
@@ -13,6 +13,7 @@
 #include <waiter/waiter.h>
 #include <log/log.h>
 
+#include "event.h"
 #include "udev.h"
 #include "pb-discover.h"
 #include "device-handler.h"
@@ -26,52 +27,7 @@ struct udev {
 	int socket;
 };
 
-static void parse_event_params(struct udev_event *event, char *buf, int len)
-{
-	int param_len, name_len, value_len;
-	struct param *param;
-	char *sep;
-
-	for (; len > 0; len -= param_len + 1, buf += param_len + 1) {
-
-		/* find the length of the whole parameter */
-		param_len = strnlen(buf, len);
-		if (!param_len) {
-			/* multiple NULs? skip over */
-			param_len = 1;
-			continue;
-		}
-
-		/* find the separator */
-		sep = memchr(buf, '=', param_len);
-		if (!sep)
-			continue;
-
-		name_len = sep - buf;
-		value_len = param_len - name_len - 1;
-
-		/* update the params array */
-		event->params = talloc_realloc(event, event->params,
-					struct param, ++event->n_params);
-		param = &event->params[event->n_params - 1];
-
-		param->name = talloc_strndup(event, buf, name_len);
-		param->value = talloc_strndup(event, sep + 1, value_len);
-	}
-}
-
-const char *udev_event_param(struct udev_event *event, const char *name)
-{
-	int i;
-
-	for (i = 0; i < event->n_params; i++)
-		if (!strcasecmp(event->params[i].name, name))
-			return event->params[i].value;
-
-	return NULL;
-}
-
-static void print_event(struct udev_event *event)
+static void udev_print_event(struct event *event)
 {
 	const char *action, *params[] = {
 		"DEVNAME", "ID_TYPE", "ID_BUS", "ID_FS_UUID", "ID_FS_LABEL",
@@ -79,63 +35,32 @@ static void print_event(struct udev_even
 	};
 	int i;
 
-	action = event->action == UDEV_ACTION_ADD ? "add" : "remove";
+	action = event->action == EVENT_ACTION_ADD ? "add" : "remove";
 
 	pb_log("udev %s event:\n", action);
 	pb_log("\tdevice: %s\n", event->device);
 
 	for (i = 0; params[i]; i++)
 		pb_log("\t%-12s => %s\n",
-				params[i], udev_event_param(event, params[i]));
+				params[i], event_get_param(event, params[i]));
 
 }
 
-static void handle_udev_message(struct udev *udev, char *buf, int len)
+static void udev_handle_message(struct udev *udev, char *buf, int len)
 {
-	char *sep, *device;
-	enum udev_action action;
-	struct udev_event *event;
-	int device_len;
-
-	/* we should see an <action>@<device>\0 at the head of the buffer */
-	sep = strchr(buf, '@');
-	if (!sep)
-		return;
+	int result;
+	struct event *event;
 
-	/* terminate the action string */
-	*sep = '\0';
-	len -= sep - buf + 1;
+	event = talloc(udev, struct event);
+	event->type = EVENT_TYPE_UDEV;
 
-	if (!strcmp(buf, "add")) {
-		action = UDEV_ACTION_ADD;
+	result = event_parse_ad_message(event, buf, len);
 
-	} else if (!strcmp(buf, "remove")) {
-		action = UDEV_ACTION_REMOVE;
-
-	} else {
+	if (result)
 		return;
-	}
-
-	/* initialise the device string */
-	device = sep + 1;
-	device_len = strnlen(device, len);
-	if (!device_len)
-		return;
-
-	/* now we have an action and a device, we can construct an event */
-	event = talloc(udev, struct udev_event);
-	event->action = action;
-	event->device = talloc_strndup(event, device, device_len);
-	event->n_params = 0;
-	event->params = NULL;
-
-	len -= device_len + 1;
-	parse_event_params(event, device + device_len + 1, len);
-
-	print_event(event);
 
+	udev_print_event(event);
 	device_handler_event(udev->handler, event);
-
 	talloc_free(event);
 
 	return;
@@ -157,7 +82,7 @@ static int udev_process(void *arg)
 	if (len == 0)
 		return 0;
 
-	handle_udev_message(udev, buf, len);
+	udev_handle_message(udev, buf, len);
 
 	return 0;
 }
--- a/discover/udev.h
+++ b/discover/udev.h
@@ -1,29 +1,11 @@
 #ifndef _UDEV_H
 #define _UDEV_H
 
-enum udev_action {
-	UDEV_ACTION_ADD,
-	UDEV_ACTION_REMOVE,
-};
-
-struct udev_event {
-	enum udev_action action;
-	char *device;
-
-	struct param {
-		char *name;
-		char *value;
-	} *params;
-	int n_params;
-};
-
 struct udev;
 struct device_handler;
 
 struct udev *udev_init(struct device_handler *handler);
 int udev_trigger(struct udev *udev);
-
 void udev_destroy(struct udev *udev);
 
-const char *udev_event_param(struct udev_event *event, const char *name);
 #endif /* _UDEV_H */
--- a/rules.mk
+++ b/rules.mk
@@ -40,7 +40,7 @@ waiter_objs = lib/waiter/waiter.o
 # daemon objs
 parser_objs = discover/parser.o discover/parser-conf.o discover/paths.o \
 	$(foreach p, $(parsers), discover/$(p)-parser.o)
-discover_objs =  discover/udev.o discover/discover-server.o \
+discover_objs = discover/event.o  discover/udev.o discover/discover-server.o \
 	discover/device-handler.o discover/paths.o discover/parser-utils.o
 
 # client objs
