about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2017-08-01 19:36:38 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2017-08-01 19:36:38 +0000
commit69099d84949a8044fdfc74e9d7ff6b9e57fc0bcd (patch)
tree9c97923869ff4f56717d7e0ac97c8588f816c798 /src
parent22a1dc61ef8e4e1c98e3a088db1dbc84d424c7a7 (diff)
downloadbcnm-69099d84949a8044fdfc74e9d7ff6b9e57fc0bcd.tar.gz
bcnm-69099d84949a8044fdfc74e9d7ff6b9e57fc0bcd.tar.xz
bcnm-69099d84949a8044fdfc74e9d7ff6b9e57fc0bcd.zip
Flesh out libwpactrl
Diffstat (limited to 'src')
-rw-r--r--src/include/bcnm/wpactrl.h101
-rw-r--r--src/libwpactrl/deps-lib/wpactrl12
-rw-r--r--src/libwpactrl/wpactrl-internal.h3
-rw-r--r--src/libwpactrl/wpactrl_ackmsg.c (renamed from src/libwpactrl/wpactrl_nextmsg.c)2
-rw-r--r--src/libwpactrl/wpactrl_command.c52
-rw-r--r--src/libwpactrl/wpactrl_end.c5
-rw-r--r--src/libwpactrl/wpactrl_env_parse.c11
-rw-r--r--src/libwpactrl/wpactrl_filter_add.c3
-rw-r--r--src/libwpactrl/wpactrl_filter_match.c18
-rw-r--r--src/libwpactrl/wpactrl_query.c8
-rw-r--r--src/libwpactrl/wpactrl_querysa.c4
-rw-r--r--src/libwpactrl/wpactrl_scan_parse.c51
-rw-r--r--src/libwpactrl/wpactrl_start.c15
-rw-r--r--src/libwpactrl/wpactrl_update.c14
-rw-r--r--src/libwpactrl/wpactrl_xchg_computedeadline.c9
-rw-r--r--src/libwpactrl/wpactrl_xchg_event.c42
-rw-r--r--src/libwpactrl/wpactrl_xchg_free.c10
-rw-r--r--src/libwpactrl/wpactrl_xchg_init.c17
-rw-r--r--src/libwpactrl/wpactrl_xchg_start.c14
-rw-r--r--src/libwpactrl/wpactrl_xchg_timeout.c15
-rw-r--r--src/libwpactrl/wpactrl_xchg_zero.c5
21 files changed, 320 insertions, 91 deletions
diff --git a/src/include/bcnm/wpactrl.h b/src/include/bcnm/wpactrl.h
index df5f2c1..1c5610c 100644
--- a/src/include/bcnm/wpactrl.h
+++ b/src/include/bcnm/wpactrl.h
@@ -5,6 +5,7 @@
 
 #include <sys/types.h>
 #include <stdint.h>
+#include <errno.h>
 #include <skalibs/gccattributes.h>
 #include <skalibs/tai.h>
 #include <skalibs/stralloc.h>
@@ -13,23 +14,23 @@
 typedef enum wparesponse_e wparesponse_t, *wparesponse_t_ref ;
 enum wparesponse_e
 {
-  WPA_ERROR = -1,
   WPA_OK = 0,
-  WPA_PONG,
-  WPA_UNKNOWNCOMMAND,
-  WPA_FAIL,
-  WPA_FAILBUSY,
-  WPA_FAILCHECKSUM,
-  WPA_FAILINVALIDPIN,
-  WPA_FAILCHANNELUNAVAILABLE,
-  WPA_FAILCHANNELUNSUPPORTED,
-  WPA_FAILINVALIDRANGE,
-  WPA_FAILTOOLONGRESPONSE,
-  WPA_FAILPBCOVERLAP,
-  WPA_FAILUNKNOWNUUID,
-  WPA_FAILNOAPSETTINGS,
-  WPA_FAILNOIFNAMEATTACH,
-  WPA_UNKNOWNRESPONSE
+  WPA_PONG = 1,
+  WPA_ERROR = -1,
+  WPA_UNKNOWNCOMMAND = -2,
+  WPA_FAIL = -3,
+  WPA_FAILBUSY = -4,
+  WPA_FAILCHECKSUM = -5,
+  WPA_FAILINVALIDPIN = -6,
+  WPA_FAILCHANNELUNAVAILABLE = -7,
+  WPA_FAILCHANNELUNSUPPORTED = -8,
+  WPA_FAILINVALIDRANGE = -9,
+  WPA_FAILTOOLONGRESPONSE = -10,
+  WPA_FAILPBCOVERLAP = -11,
+  WPA_FAILUNKNOWNUUID = -12,
+  WPA_FAILNOAPSETTINGS = -13,
+  WPA_FAILNOIFNAMEMATCH = -14,
+  WPA_UNKNOWNRESPONSE = -999
 } ;
 
 typedef struct wpactrl_s wpactrl_t, *wpactrl_t_ref ;
@@ -38,29 +39,31 @@ struct wpactrl_s
   int fds ;
   int fda ;
   uint32_t options ;
+  tain_t tto ;
   size_t datahead ;
   stralloc data ;
   stralloc filters ;
 } ;
-#define WPACTRL_ZERO { .fds = -1, .fda = -1, .options = 0, .datahead = 0, .data = STRALLOC_ZERO, .filters = STRALLOC_ZERO }
+#define WPACTRL_ZERO { .fds = -1, .fda = -1, .options = 0, .tto = TAIN_ZERO, .datahead = 0, .data = STRALLOC_ZERO, .filters = STRALLOC_ZERO }
 
 #define WPACTRL_OPTION_NOFILTER 0x0001U
 
 extern wpactrl_t const wpactrl_zero ;
 
-extern int wpactrl_start (wpactrl_t *, char const *, tain_t const *, tain_t *) ;
-#define wpactrl_start_g(a, path, deadline) wpactrl_start(a, path, (deadline), &STAMP)
+extern int wpactrl_start (wpactrl_t *, char const *, unsigned int, tain_t *) ;
+#define wpactrl_start_g(a, path, timeout) wpactrl_start(a, path, (timeout), &STAMP)
 extern void wpactrl_end (wpactrl_t *) ;
 
-extern wparesponse_t wpactrl_command (wpactrl_t *, char const *, size_t, tain_t const *, tain_t *) ;
-#define wpactrl_command_g(a, q, qlen, deadline) wpactrl_command(a, q, qlen, (deadline), &STAMP)
-extern ssize_t wpactrl_query (wpactrl_t *, char const *, size_t, char *, size_t, tain_t const *, tain_t *) ;
-#define wpactrl_query_g(a, q, qlen, ans, ansmax, deadline) wpactrl_query(a, q, qlen, ans, ansmax, (deadline), &STAMP)
-extern int wpactrl_querysa (wpactrl_t *, char const *, size_t, stralloc *, tain_t const *, tain_t *) ;
-#define wpactrl_querysa_g(a, q, qlen, sa, deadline) wpactrl_querysa(a, q, qlen, sa, (deadline), &STAMP)
+extern wparesponse_t wpactrl_command (wpactrl_t *, char const *, size_t, tain_t *) ;
+#define wpactrl_command_g(a, q, qlen) wpactrl_command(a, q, (qlen), &STAMP)
+extern ssize_t wpactrl_query (wpactrl_t *, char const *, size_t, char *, size_t, tain_t *) ;
+#define wpactrl_query_g(a, q, qlen, ans, ansmax) wpactrl_query(a, q, qlen, ans, (ansmax), &STAMP)
+extern int wpactrl_querysa (wpactrl_t *, char const *, size_t, stralloc *, tain_t *) ;
+#define wpactrl_querysa_g(a, q, qlen, sa) wpactrl_querysa(a, q, qlen, (sa), &STAMP)
 
 extern int wpactrl_filter_add (wpactrl_t *, char const *) ;
 extern void wpactrl_filter_remove (wpactrl_t *, char const *) ;
+extern int wpactrl_filter_match (wpactrl_t const *, char const *, size_t) ;
 
 #define wpactrl_filter_activate(a) ((a)->options &= ~(uint32_t)WPACTRL_OPTION_NOFILTER)
 #define wpactrl_filter_deactivate(a) ((a)->options |= WPACTRL_OPTION_NOFILTER)
@@ -70,7 +73,7 @@ extern char *wpactrl_msg (wpactrl_t *) gccattr_pure ;
 extern void wpactrl_ackmsg (wpactrl_t *) ;
 
 
- /* High-level functions */
+ /* Helper functions for parsing answers from wpa_supplicant */
 
 typedef struct wpactrl_scanres_s wpactrl_scanres_t, *wpactrl_scanres_t_ref ;
 struct wpactrl_scanres_s
@@ -78,13 +81,53 @@ struct wpactrl_scanres_s
   char bssid[6] ;
   uint16_t frequency ;
   uint16_t signal_level ;
-  uint32_t flags ;
-  size_t ssid ;
+  size_t flags_start ;
+  size_t flags_len ;
+  size_t ssid_start ;
+  size_t ssid_len ;
 } ;
-#define WPACTRL_SCANRES_ZERO { .bssid = "\0\0\0\0\0", .frequency = 0, .signal_level = 0, .flags = 0, .ssid = 0 }
+#define WPACTRL_SCANRES_ZERO { .bssid = "\0\0\0\0\0", .frequency = 0, .signal_level = 0, .flags_start = 0, .flags_len = 0, .ssid_start = 0, .ssid_len = 0 }
 
 extern int wpactrl_scan_parse (char const *, size_t, genalloc * /* wpactrl_scanres_t */, stralloc *) ;
+extern unsigned int wpactrl_env_parse (char *, size_t) ;
+
+
+ /* Higher-level functions for easy iopause */
+
+typedef int wpactrl_xchg_func_t (wpactrl_t *, char const *, size_t, void *, tain_t *) ;
+typedef wpactrl_xchg_func_t *wpactrl_xchg_func_t_ref ;
+
+typedef struct wpactrl_xchgitem_s wpactrl_xchgitem_t, *wpactrl_xchgitem_t_ref ;
+struct wpactrl_xchgitem_s
+{
+  char const *filter ;
+  wpactrl_xchg_func_t_ref f ;
+} ;
+
+typedef struct wpactrl_xchg_s wpactrl_xchg_t, *wpactrl_xchg_t_ref ;
+struct wpactrl_xchg_s
+{
+  stralloc sa ;
+  wpactrl_xchgitem_t const *tab ; 
+  unsigned int n ;
+  unsigned int i ;
+  tain_t deadline ;
+  int status ;
+  void *aux ;
+} ;
+#define WPACTRL_XCHG_ZERO { .sa = STRALLOC_ZERO, .tab = 0, .n = 0, .i = 0, .deadline = TAIN_ZERO, .status = ECONNABORTED, .aux = 0 }
+#define WPACTRL_XCHG_INIT(array, size, limit, extra) { .sa = STRALLOC_ZERO, .tab = array, .n = size, .i = 0, .deadline = limit, .status = ECONNABORTED, .aux = extra }
+
+extern wpactrl_xchg_t const wpactrl_xchg_zero ;
+extern void wpactrl_xchg_free (wpactrl_xchg_t *) ;
+extern int wpactrl_xchg_init (wpactrl_xchg_t *, wpactrl_xchgitem_t const *, unsigned int, tain_t const *, void *) ;
+extern int wpactrl_xchg_start (wpactrl_t *, wpactrl_xchg_t *) ;
 
+extern void wpactrl_xchg_computedeadline (wpactrl_xchg_t const *, tain_t *) ;
+extern int wpactrl_xchg_timeout (wpactrl_xchg_t *, tain_t const *) ;
+#define wpactrl_xchg_timeout_g(dt) wpactrl_xchg_timeout((dt), &STAMP)
+extern int wpactrl_xchg_event (wpactrl_t *, wpactrl_xchg_t *, tain_t *) ;
+#define wpactrl_xchg_event_g(a, dt) wpactrl_xchg_event(a, (dt), &STAMP)
 
 
  /*
diff --git a/src/libwpactrl/deps-lib/wpactrl b/src/libwpactrl/deps-lib/wpactrl
index 98cd9e6..1943682 100644
--- a/src/libwpactrl/deps-lib/wpactrl
+++ b/src/libwpactrl/deps-lib/wpactrl
@@ -1,12 +1,24 @@
+wpactrl_ackmsg.o
 wpactrl_command.o
 wpactrl_end.o
+wpactrl_env_parse.o
 wpactrl_fd_recv.o
 wpactrl_fd_timed_recv.o
 wpactrl_filter_add.o
 wpactrl_filter_exact_search.o
+wpactrl_filter_match.o
 wpactrl_filter_remove.o
+wpactrl_msg.o
 wpactrl_query.o
 wpactrl_querysa.o
+wpactrl_scan_parse.o
 wpactrl_start.o
 wpactrl_update.o
+wpactrl_xchg_computedeadline.o
+wpactrl_xchg_event.o
+wpactrl_xchg_free.o
+wpactrl_xchg_init.o
+wpactrl_xchg_start.o
+wpactrl_xchg_timeout.o
+wpactrl_xchg_zero.o
 wpactrl_zero.o
diff --git a/src/libwpactrl/wpactrl-internal.h b/src/libwpactrl/wpactrl-internal.h
index ddd2597..50f2fbd 100644
--- a/src/libwpactrl/wpactrl-internal.h
+++ b/src/libwpactrl/wpactrl-internal.h
@@ -4,6 +4,7 @@
 #define BCNM_WPACTRL_INTERNAL_H
 
 #include <sys/types.h>
+#include <stdint.h>
 #include <skalibs/gccattributes.h>
 #include <skalibs/tai.h>
 #include <bcnm/wpactrl.h>
@@ -15,4 +16,6 @@ extern ssize_t wpactrl_fd_recv (int, char *, size_t) ;
 extern ssize_t wpactrl_fd_timed_recv (int, char *, size_t, tain_t const *, tain_t *) ;
 extern size_t wpactrl_filter_exact_search (wpactrl_t const *, char const *) gccattr_pure ;
 
+extern size_t wpactrl_xchgset_find (wpactrl_t const *, uint32_t) ;
+
 #endif
diff --git a/src/libwpactrl/wpactrl_nextmsg.c b/src/libwpactrl/wpactrl_ackmsg.c
index 4954db9..6d0d3b6 100644
--- a/src/libwpactrl/wpactrl_nextmsg.c
+++ b/src/libwpactrl/wpactrl_ackmsg.c
@@ -3,7 +3,7 @@
 #include <string.h>
 #include <bcnm/wpactrl.h>
 
-void wpactrl_nextmsg (wpactrl_t *a)
+void wpactrl_ackmsg (wpactrl_t *a)
 {
   if (a->datahead < a->data.len) a->datahead += strlen(a->data.s + a->datahead) + 1 ;
   if (a->datahead >= a->data.len)
diff --git a/src/libwpactrl/wpactrl_command.c b/src/libwpactrl/wpactrl_command.c
index c29a58a..34e31c2 100644
--- a/src/libwpactrl/wpactrl_command.c
+++ b/src/libwpactrl/wpactrl_command.c
@@ -6,36 +6,42 @@
 
 #define WPARESPONSE_MAXLEN 28
 
-wparesponse_t wpactrl_command (wpactrl_t *a, char const *s, size_t len, tain_t const *deadline, tain_t *stamp)
+struct wparesponse_map_s
 {
-  static char const *wparesponses[] =
+  char const *s ;
+  wparesponse_t r ;
+} ;
+
+wparesponse_t wpactrl_command (wpactrl_t *a, char const *s, size_t len, tain_t *stamp)
+{
+  static struct wparesponse_map_s const wparesponses[] =
   {
-    "OK\n",
-    "PONG\n",
-    "UNKNOWN COMMAND\n",
-    "FAIL\n",
-    "FAIL-BUSY\n",
-    "FAIL-CHECKSUM\n",
-    "FAIL-INVALID-PIN\n",
-    "FAIL-CHANNEL-UNAVAILABLE\n",
-    "FAIL-CHANNEL-UNSUPPORTED\n",
-    "FAIL-Invalid range\n",
-    "FAIL-Too long response\n",
-    "FAIL-PBC-OVERLAP\n",
-    "FAIL-UNKNOWN-UUID\n",
-    "FAIL-NO-AP-SETTINGS\n",
-    "FAIL-NO-IFNAME-MATCH\n",
-    0
+    {  "OK\n", WPA_OK },
+    { "PONG\n", WPA_PONG },
+    { "UNKNOWN COMMAND\n", WPA_UNKNOWNCOMMAND },
+    { "FAIL\n", WPA_FAIL },
+    { "FAIL-BUSY\n", WPA_FAILBUSY },
+    { "FAIL-CHECKSUM\n", WPA_FAILCHECKSUM },
+    { "FAIL-INVALID-PIN\n", WPA_FAILINVALIDPIN },
+    { "FAIL-CHANNEL-UNAVAILABLE\n", WPA_FAILCHANNELUNAVAILABLE },
+    { "FAIL-CHANNEL-UNSUPPORTED\n", WPA_FAILCHANNELUNSUPPORTED },
+    { "FAIL-Invalid range\n", WPA_FAILINVALIDRANGE },
+    { "FAIL-Too long response\n", WPA_FAILTOOLONGRESPONSE },
+    { "FAIL-PBC-OVERLAP\n", WPA_FAILPBCOVERLAP },
+    { "FAIL-UNKNOWN-UUID\n", WPA_FAILUNKNOWNUUID },
+    { "FAIL-NO-AP-SETTINGS\n", WPA_FAILNOAPSETTINGS },
+    { "FAIL-NO-IFNAME-MATCH\n", WPA_FAILNOIFNAMEMATCH },
+    { 0, WPA_UNKNOWNRESPONSE }
   } ;
   char buf[WPARESPONSE_MAXLEN] ;
-  ssize_t r = wpactrl_query(a, s, len, buf, WPARESPONSE_MAXLEN, deadline, stamp) ;
+  ssize_t r = wpactrl_query(a, s, len, buf, WPARESPONSE_MAXLEN, stamp) ;
   if (r < 0) return WPA_ERROR ;
   if (!r) return (errno = EPIPE, WPA_ERROR) ;
   {
-    wparesponse_t i = 0 ;
-    for (; wparesponses[i] ; i++)
-      if (!strncmp(buf, wparesponses[i], r))
+    unsigned int i = 0 ;
+    for (; wparesponses[i].s ; i++)
+      if (!strncmp(buf, wparesponses[i].s, r))
         break ;
-    return i ;
+    return wparesponses[i].r ;
   }
 }
diff --git a/src/libwpactrl/wpactrl_end.c b/src/libwpactrl/wpactrl_end.c
index 40fdb4a..a14b6c8 100644
--- a/src/libwpactrl/wpactrl_end.c
+++ b/src/libwpactrl/wpactrl_end.c
@@ -1,11 +1,14 @@
 /* ISC license. */
 
+#include <skalibs/stralloc.h>
 #include <skalibs/djbunix.h>
 #include <bcnm/wpactrl.h>
 
-void wpactrl_free (wpactrl_t *a)
+void wpactrl_end (wpactrl_t *a)
 {
   fd_close(a->fda) ;
   fd_close(a->fds) ;
+  stralloc_free(&a->filters) ;
+  stralloc_free(&a->data) ;
   *a = wpactrl_zero ;
 }
diff --git a/src/libwpactrl/wpactrl_env_parse.c b/src/libwpactrl/wpactrl_env_parse.c
new file mode 100644
index 0000000..c722fd5
--- /dev/null
+++ b/src/libwpactrl/wpactrl_env_parse.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <bcnm/wpactrl.h>
+
+unsigned int wpactrl_env_parse (char *s, size_t len)
+{
+  size_t i = 0 ;
+  unsigned int n = 0 ;
+  for (; i < len ; i++) if (s[i] == '\n') (s[i] = 0, n++) ;
+  return n ;
+}
diff --git a/src/libwpactrl/wpactrl_filter_add.c b/src/libwpactrl/wpactrl_filter_add.c
index 8896e23..52e5b3a 100644
--- a/src/libwpactrl/wpactrl_filter_add.c
+++ b/src/libwpactrl/wpactrl_filter_add.c
@@ -1,12 +1,13 @@
 /* ISC license. */
 
 #include <string.h>
+#include <errno.h>
 #include <skalibs/stralloc.h>
 #include <bcnm/wpactrl.h>
 #include "wpactrl-internal.h"
 
 int wpactrl_filter_add (wpactrl_t *a, char const *s)
 {
-  if (wpactrl_filter_exact_search(a, s) < a->filters.len) return 1 ;
+  if (wpactrl_filter_exact_search(a, s) < a->filters.len) return (errno = EBUSY, 0) ;
   return stralloc_catb(&a->filters, s, strlen(s)) ;
 }
diff --git a/src/libwpactrl/wpactrl_filter_match.c b/src/libwpactrl/wpactrl_filter_match.c
new file mode 100644
index 0000000..30ed0ff
--- /dev/null
+++ b/src/libwpactrl/wpactrl_filter_match.c
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#include <string.h>
+#include <bcnm/wpactrl.h>
+
+int wpactrl_filter_match (wpactrl_t const *a, char const *s, size_t len)
+{
+  size_t filterlen = a->filters.len ;
+  char const *filters = a->filters.s ;
+  while (filterlen)
+  {
+    size_t flen = strlen(filters) ;
+    if (len >= flen && !strncmp(filters, s, flen)) return 1 ;
+    filters += flen+1 ;
+    filterlen -= flen+1 ;
+  }
+  return 0 ;
+}
diff --git a/src/libwpactrl/wpactrl_query.c b/src/libwpactrl/wpactrl_query.c
index 951c536..b03899f 100644
--- a/src/libwpactrl/wpactrl_query.c
+++ b/src/libwpactrl/wpactrl_query.c
@@ -4,8 +4,10 @@
 #include <bcnm/wpactrl.h>
 #include "wpactrl-internal.h"
 
-ssize_t wpactrl_query (wpactrl_t *a, char const *q, size_t qlen, char *ans, size_t ansmax, tain_t const *deadline, tain_t *stamp)
+ssize_t wpactrl_query (wpactrl_t *a, char const *q, size_t qlen, char *ans, size_t ansmax, tain_t *stamp)
 {
-  if (!ipc_timed_send(a->fds, q, qlen, deadline, stamp)) return WPA_ERROR ;
-  return wpactrl_fd_timed_recv(a->fds, ans, ansmax, deadline, stamp) ;
+  tain_t deadline ;
+  tain_add(&deadline, stamp, &a->tto) ;
+  if (!ipc_timed_send(a->fds, q, qlen, &deadline, stamp)) return WPA_ERROR ;
+  return wpactrl_fd_timed_recv(a->fds, ans, ansmax, &deadline, stamp) ;
 }
diff --git a/src/libwpactrl/wpactrl_querysa.c b/src/libwpactrl/wpactrl_querysa.c
index fe06202..887941f 100644
--- a/src/libwpactrl/wpactrl_querysa.c
+++ b/src/libwpactrl/wpactrl_querysa.c
@@ -5,10 +5,10 @@
 #include <bcnm/wpactrl.h>
 #include "wpactrl-internal.h"
 
-int wpactrl_querysa (wpactrl_t *a, char const *s, size_t len, stralloc *sa, tain_t const *deadline, tain_t *stamp)
+int wpactrl_querysa (wpactrl_t *a, char const *s, size_t len, stralloc *sa, tain_t *stamp)
 {
   char buf[WPACTRL_PACKET_MAX] ;
-  ssize_t r = wpactrl_query(a, s, len, buf, WPACTRL_PACKET_MAX, deadline, stamp) ;
+  ssize_t r = wpactrl_query(a, s, len, buf, WPACTRL_PACKET_MAX, stamp) ;
   if (r < 0) return 0 ;
   if (!r) return (errno = EPIPE, 0) ;
   return stralloc_catb(sa, buf, r) ;
diff --git a/src/libwpactrl/wpactrl_scan_parse.c b/src/libwpactrl/wpactrl_scan_parse.c
index 24f83c8..ab4a9fe 100644
--- a/src/libwpactrl/wpactrl_scan_parse.c
+++ b/src/libwpactrl/wpactrl_scan_parse.c
@@ -17,35 +17,60 @@ static size_t bssid_scan (char const *s, char *bssid)
   char sep[6] = ":::::\t" ;
   for (; i < 6 ; i++)
   {
-    if (!ucharn_scan(s, bssid + i, 1)) return 0 ;
-    if (s[2] != sep[i]) return 0 ;
+    if (!ucharn_scan(s, bssid + i, 1)) goto eproto ;
+    if (s[2] != sep[i]) goto eproto ;
     s += 3 ;
   }
   return 18 ;
+
+ eproto:
+  return (errno = EPROTO, 0) ;
 }
 
-static size_t flags_scan (char const *s, uint32_t *flags)
+static int flags_scan (char const *s, size_t len, stralloc *sa)
 {
-  return 0 ;
+  while (len)
+  {
+    size_t pos ;
+    if (*s++ != '[') goto eproto ;
+    len-- ;
+    pos = byte_chr(s, len, ']') ;
+    if (pos >= len || !pos) goto eproto ;
+    if (!stralloc_catb(sa, s, pos) || !stralloc_0(sa)) return 0 ;
+    s += pos + 1 ; len -= pos + 1 ;
+  }
+  return 1 ;
+
+ eproto:
+  return (errno = EPROTO, 0) ;
 }
 
 static int wpactrl_scan_parse_one (char const *s, size_t len, wpactrl_scanres_t *thing, stralloc *sa)
 {
+  wpactrl_scanres_t sr ;
   size_t pos = byte_chr(s, len, '\t') ;
-  if (pos >= len) return 0 ;
-  if (pos != 18) return 0 ;
-  if (bssid_scan(s, thing->bssid) != pos) return 0 ;
+  if (pos >= len) goto eproto ;
+  if (pos != 18) goto eproto ;
+  if (bssid_scan(s, sr.bssid) != pos) goto eproto ;
   s += pos + 1 ; len -= pos + 1 ;
   pos = byte_chr(s, len, '\t') ;
-  if (pos >= len) return 0 ;
-  if (uint16_scan(s, &thing->frequency) != pos) return 0 ;
+  if (pos >= len) goto eproto ;
+  if (uint16_scan(s, &sr.frequency) != pos) goto eproto ;
   s += pos + 1 ; len -= pos + 1 ;
   pos = byte_chr(s, len, '\t') ;
-  if (pos >= len) return 0 ;
-  if (flags_scan(s, &thing->flags) != pos) return 0 ;
+  if (pos >= len) goto eproto ;
+  sr.flags_start = sa->len ;
+  if (!flags_scan(s, pos, sa)) goto eproto ;
   s += pos + 1 ; len -= pos + 1 ;
-  thing->ssid = sa->len ;
-  return stralloc_catb(sa, s, len - 1) && stralloc_0(sa) ;
+  sr.flags_len = sa->len - sr.flags_start ;
+  sr.ssid_start = sa->len ;
+  sr.ssid_len = len - 1 ;
+  if (!stralloc_catb(sa, s, len - 1) || !stralloc_0(sa)) return 0 ;
+  *thing = sr ;
+  return 1 ;
+
+ eproto:
+  return (errno = EPROTO, 0) ;
 }
 
 int wpactrl_scan_parse (char const *s, size_t len, genalloc *ga, stralloc *sa)
diff --git a/src/libwpactrl/wpactrl_start.c b/src/libwpactrl/wpactrl_start.c
index 86a266b..80018d5 100644
--- a/src/libwpactrl/wpactrl_start.c
+++ b/src/libwpactrl/wpactrl_start.c
@@ -8,24 +8,29 @@
 #include <bcnm/wpactrl.h>
 #include "wpactrl-internal.h"
 
-int wpactrl_start (wpactrl_t *a, char const *path, tain_t const *deadline, tain_t *stamp)
+int wpactrl_start (wpactrl_t *a, char const *path, unsigned int timeout, tain_t *stamp)
 {
+  tain_t tto, deadline ;
   int fda, fds ;
+  if (timeout) tain_from_millisecs(&tto, timeout) ;
+  else tto = tain_infinite_relative ;
   fds = ipc_datagram_nbcoe() ;
   if (fds < 0) goto err ;
-  if (!ipc_timed_connect(fds, path, deadline, stamp)) goto errs ;
+  tain_add(&deadline, &tto, stamp) ;
+  if (!ipc_timed_connect(fds, path, &deadline, stamp)) goto errs ;
   fda = ipc_datagram_nbcoe() ;
   if (fda < 0) goto errs ;
-  if (!ipc_timed_connect(fda, path, deadline, stamp)) goto erra ;
-  if (!ipc_timed_send(fda, "ATTACH", 6, deadline, stamp)) goto erra ;
+  if (!ipc_timed_connect(fda, path, &deadline, stamp)) goto erra ;
+  if (!ipc_timed_send(fda, "ATTACH", 6, &deadline, stamp)) goto erra ;
   {
     ssize_t r ;
     char answer[3] ;
-    r = wpactrl_fd_timed_recv(fda, answer, 3, deadline, stamp) ;
+    r = wpactrl_fd_timed_recv(fda, answer, 3, &deadline, stamp) ;
     if (r != 3 || memcmp(answer, "OK\n", 3)) goto erra ;
   }
   a->fds = fds ;
   a->fda = fda ;
+  a->tto = tto ;
   return 1 ;
 
  erra:
diff --git a/src/libwpactrl/wpactrl_update.c b/src/libwpactrl/wpactrl_update.c
index 0462cbe..e5d8db1 100644
--- a/src/libwpactrl/wpactrl_update.c
+++ b/src/libwpactrl/wpactrl_update.c
@@ -8,18 +8,6 @@
 #include <bcnm/wpactrl.h>
 #include "wpactrl-internal.h"
 
-static inline int filter_search (char const *s, size_t len, char const *filters, size_t filterlen)
-{
-  while (filterlen)
-  {
-    size_t flen = strlen(filters) ;
-    if (len >= flen && !strncmp(filters, s, flen)) return 1 ;
-    filters += flen+1 ;
-    filterlen -= flen+1 ;
-  }
-  return 0 ;
-}
-
 static inline int validate (char const *s, size_t len)
 {
   if (len < 4) return 0 ;
@@ -41,7 +29,7 @@ int wpactrl_update (wpactrl_t *a)
     if (r < 0) return -1 ;
     if (!r) break ;
     if (a->options & WPACTRL_OPTION_NOFILTER
-     || (validate(buf, r) && filter_search(buf, r, a->filters.s, a->filters.len)))
+     || (validate(buf, r) && wpactrl_filter_match(a, buf, r)))
     {
       buf[r] = 0 ;
       if (!stralloc_catb(&a->data, buf, r+1)) return -1 ;
diff --git a/src/libwpactrl/wpactrl_xchg_computedeadline.c b/src/libwpactrl/wpactrl_xchg_computedeadline.c
new file mode 100644
index 0000000..baaac77
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_computedeadline.c
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#include <skalibs/tai.h>
+#include <bcnm/wpactrl.h>
+
+void wpactrl_xchg_computedeadline (wpactrl_xchg_t const *dt, tain_t *deadline)
+{
+  if (tain_less(&dt->deadline, deadline)) *deadline = dt->deadline ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_event.c b/src/libwpactrl/wpactrl_xchg_event.c
new file mode 100644
index 0000000..6d25295
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_event.c
@@ -0,0 +1,42 @@
+/* ISC license. */
+
+#include <string.h>
+#include <skalibs/stralloc.h>
+#include <bcnm/wpactrl.h>
+
+static inline size_t wpactrl_findmsg (wpactrl_t *a, char const *filter)
+{
+  size_t filterlen = strlen(filter) ;
+  size_t i = 0 ;
+  while (i < a->data.len)
+  {
+    if (!strncmp(a->data.s + i, filter, filterlen)) break ;
+    i += strlen(a->data.s + i) + 1 ;
+  }
+  return i ;
+}
+
+int wpactrl_xchg_event (wpactrl_t *a, wpactrl_xchg_t *dt, tain_t *stamp)
+{
+  size_t pos, len ;
+  if (dt->i >= dt->n) return 2 ;
+  pos = wpactrl_findmsg(a, dt->tab[dt->i].filter) ;
+  if (pos >= a->data.len) return 0 ;
+  dt->sa.len = 0 ;
+  len = strlen(a->data.s + pos) + 1 ;
+  if (dt->i == dt->n - 1)
+  {
+    if (!stralloc_catb(&dt->sa, a->data.s + pos, len)) return -1 ;
+    memmove(a->data.s + pos, a->data.s + pos + len, a->data.len - len) ;
+    a->data.len -= len ;
+    dt->status = 0 ;
+    wpactrl_filter_remove(a, dt->tab[dt->i].filter) ;
+    return 1 ;
+  }
+  if (!(*dt->tab[dt->i].f)(a, a->data.s + pos, len - 1, dt->aux, stamp)) return -1 ;
+  memmove(a->data.s + pos, a->data.s + pos + len, a->data.len - len) ;
+  a->data.len -= len ;
+  wpactrl_filter_remove(a, dt->tab[dt->i].filter) ;
+  if (!wpactrl_filter_add(a, dt->tab[++dt->i].filter)) return -1 ;
+  return 0 ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_free.c b/src/libwpactrl/wpactrl_xchg_free.c
new file mode 100644
index 0000000..e244a52
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_free.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/stralloc.h>
+#include <bcnm/wpactrl.h>
+
+void wpactrl_xchg_free (wpactrl_xchg_t *dt)
+{
+  stralloc_free(&dt->sa) ;
+  *dt = wpactrl_xchg_zero ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_init.c b/src/libwpactrl/wpactrl_xchg_init.c
new file mode 100644
index 0000000..37006dd
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_init.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/stralloc.h>
+#include <bcnm/wpactrl.h>
+
+int wpactrl_xchg_init (wpactrl_xchg_t *dt, wpactrl_xchgitem_t const *tab, unsigned int n, tain_t const *limit, void *aux)
+{
+  dt->sa.len = 0 ;
+  dt->tab = tab ;
+  dt->n = n ;
+  dt->i = 0 ;
+  dt->deadline = *limit ;
+  dt->status = ECONNABORTED ;
+  dt->aux = aux ;
+  return 1 ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_start.c b/src/libwpactrl/wpactrl_xchg_start.c
new file mode 100644
index 0000000..3b45f3e
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_start.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/tai.h>
+#include <bcnm/wpactrl.h>
+
+int wpactrl_xchg_start (wpactrl_t *a, wpactrl_xchg_t *dt)
+{
+  if (!dt->n) return (errno = EINVAL, 0) ;
+  if (!wpactrl_filter_add(a, dt->tab[0].filter)) return 0 ;
+  dt->i = 0 ;
+  dt->status = EAGAIN ;
+  return 1 ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_timeout.c b/src/libwpactrl/wpactrl_xchg_timeout.c
new file mode 100644
index 0000000..01907ea
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_timeout.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/tai.h>
+#include <bcnm/wpactrl.h>
+
+int wpactrl_xchg_timeout (wpactrl_xchg_t *dt, tain_t const *stamp)
+{
+  if (!tain_less(stamp, &dt->deadline))
+  {
+    dt->status = ETIMEDOUT ;
+    return 1 ;
+  }
+  else return 0 ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_zero.c b/src/libwpactrl/wpactrl_xchg_zero.c
new file mode 100644
index 0000000..28e51eb
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_zero.c
@@ -0,0 +1,5 @@
+/* ISC license. */
+
+#include <bcnm/wpactrl.h>
+
+wpactrl_xchg_t const wpactrl_xchg_zero = WPACTRL_XCHG_ZERO ;