summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2017-08-10 20:51:40 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2017-08-10 20:51:40 +0000
commit5445a1d653cddb7b9321c87aec6b025c7627fe1d (patch)
tree1fad42bb0b4abbda9c35eb3ff829fb84bbcb8d78
parentbabf43abf301d072192bda1f72a47440c354b072 (diff)
downloadbcnm-5445a1d653cddb7b9321c87aec6b025c7627fe1d.tar.gz
bcnm-5445a1d653cddb7b9321c87aec6b025c7627fe1d.tar.xz
bcnm-5445a1d653cddb7b9321c87aec6b025c7627fe1d.zip
Complete first draft of libwpactrl, with documentation
-rw-r--r--doc/libwpactrl/index.html153
-rw-r--r--package/deps.mak8
-rw-r--r--src/include/bcnm/wpactrl.h26
-rw-r--r--src/libwpactrl/deps-lib/wpactrl4
-rw-r--r--src/libwpactrl/wpactrl_filter_match.c2
-rw-r--r--src/libwpactrl/wpactrl_startscan.c30
-rw-r--r--src/libwpactrl/wpactrl_xchg_cbres_free.c11
-rw-r--r--src/libwpactrl/wpactrl_xchg_cbres_zero.c5
-rw-r--r--src/libwpactrl/wpactrl_xchg_event.c22
-rw-r--r--src/libwpactrl/wpactrl_xchg_free.c10
-rw-r--r--src/libwpactrl/wpactrl_xchg_init.c2
-rw-r--r--src/libwpactrl/wpactrl_xchg_timeout.c3
12 files changed, 227 insertions, 49 deletions
diff --git a/doc/libwpactrl/index.html b/doc/libwpactrl/index.html
index 72ee538..e5823da 100644
--- a/doc/libwpactrl/index.html
+++ b/doc/libwpactrl/index.html
@@ -288,42 +288,47 @@ pointed to by <em>storage</em>.
 string.
 </p>
 
-<h3> High-level functions for common calls to wpa_supplicant </h3>
+<p>
+<code> void wpactrl_xchg_cbres_free (wpactrl_xchg_cbres_t *res) </code> <br />
+Frees the heap memory used by the object pointed to by <em>res</em>.
+</p>
+
+<h3> User functions for common calls to wpa_supplicant </h3>
 
 <p>
-</code> int wpactrl_addnetwork (wpactrl_t *a, uint32_t *id, tain_t *stamp) </code> </br>
+<code> int wpactrl_addnetwork (wpactrl_t *a, uint32_t *id, tain_t *stamp) </code> </br>
 Tells wpa_supplicant to create a new network. If it fails, returns 0. If it
 succeeds, stores the new network id in <em>*id</em> and returns 1.
 </p>
 
 <p>
-</code> wparesponse_t wpactrl_removenetwork (wpactrl_t *a, uint32_t id, tain_t *stamp) </code> </br>
+<code> wparesponse_t wpactrl_removenetwork (wpactrl_t *a, uint32_t id, tain_t *stamp) </code> </br>
 Tells wpa_supplicant to remove the network with id <em>id</em>. Returns the
 response code of wpa_supplicant: WPA_OK on success, WPA_FAIL or something
 else on failure.
 </p>
 
 <p>
-</code> int wpactrl_findnetwork (wpactrl_t *a, char const *ssid, uint32_t *id, tain_t *stamp) </code> </br>
+<code> int wpactrl_findnetwork (wpactrl_t *a, char const *ssid, uint32_t *id, tain_t *stamp) </code> </br>
 Finds the network id (as seen by wpa_supplicant) of the network with ssid <em>ssid</em>.
 Stores it into <em>*id</em> if found, and returns 1. Returns 0 if not found;
 returns -1 (and sets errno) if an error occurs.
 </p>
 
 <p>
-</code> wparesponse_t wpactrl_setnetworkoption (wpactrl_t *a, uint32_t id, char const *var, char const *val, tain_t *stamp) </code> </br>
+<code> wparesponse_t wpactrl_setnetworkoption (wpactrl_t *a, uint32_t id, char const *var, char const *val, tain_t *stamp) </code> </br>
 Sets parameter <em>var</em> to value <em>val</em> for network <em>id</em>.
 Returns the response code of wpa_supplicant, most likely WPA_OK or WPA_FAIL.
 </p>
 
 <p>
-</code> wparesponse_t wpactrl_selectnetwork (wpactrl_t *a, uint32_t id, tain_t *stamp) </code> </br>
+<code> wparesponse_t wpactrl_selectnetwork (wpactrl_t *a, uint32_t id, tain_t *stamp) </code> </br>
 Selects network <em>id</em> to associate with.
 Returns the response code of wpa_supplicant, most likely WPA_OK or WPA_FAIL.
 </p>
 
 <p>
-</code> int wpactrl_associate (wpactrl_t *, char const *ssid, char const *psk, tain_t *stamp) </code> </br>
+<code> int wpactrl_associate (wpactrl_t *, char const *ssid, char const *psk, tain_t *stamp) </code> </br>
 Tells wpa_supplicant to associate with the wifi network having the ssid <em>ssid</em>,
 creating it if it's not already known by wpa_supplicant. If <em>psk</em> is NULL,
 the network will be assumed open and authentication will use a NONE protocol.
@@ -332,18 +337,140 @@ WPA-PSK or WPA2-PSK, and <em>psk</em> will be sent as pre-shared key.
 The function returns 1 on success, or 0 if something went wrong.
 </p>
 
-<h3> Medium-level functions to use with an asynchronous event loop </h3>
+<p>
+<code> int wpactrl_startscan (wpactrl_t *a, wpactrl_xchgitem_t *item, wpactrl_xchg_cbres_t *res, tain_t const *limit, tain_t *stamp) </code> </br>
+Asks wpa_supplicant to start a scan. Sets up the <tt>wpactrl_xchgitem_t</tt>
+structure pointed to by <em>item</em> so it can be used in an asynchronous
+event loop to check for availability of the scan results (see below).
+<em>limit</em> is an absolute deadline after which the scan should be
+considered failed: if the current time goes over <em>limit</em>, then
+<tt>wpactrl_xchg_timeout()</tt> will report a timeout on <em>item</em>.
+But if <tt>wpactrl_xchg_event()</tt> reports that an event occurs on
+<em>item</em>, instead, the results will be available in the
+<tt>wpactrl_xchg_cbres_t</tt> structure pointed to by <em>res</em>:
+<em>res&arr;parsed</em> will be a
+<a href="//skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
+made of <tt>wpactrl_scanres_t</tt> objects, constructed by the
+<tt>wpactrl_scan_parse()</tt> function, and <em>res&arr;storage</em> will
+be the associated storage.
+<tt>wpactrl_startscan()</tt> returns 0 (and sets errno) if an error
+occurs, and 1 if the scan is properly started.
+</p>
+
+<h3> Functions to use within an asynchronous event loop </h3>
+
+<p>
+<code> int wpactrl_xchg_init (wpactrl_xchg_t *dt, wpactrl_xchgitem_t const *tab, unsigned int tablen, tain_t const *limit, void *aux) </code> </br>
+Initializes the <tt>wpactrl_xchg_t</tt> structure pointed to by <em>dt</em>.
+Returns 0 on failure and 1 on success.
+</p>
+
+<p>
+A <tt>wpactrl_xchg_t</tt> contains the state for an asynchronous call to
+wpa_supplicant (i.e. a command has been sent and we're now waiting on
+reception of an event on the attached interface). It is initialized with
+the <em>tab</em>, <em>n</em> and <em>aux</em> values.
+</p>
+
+<p>
+<em>aux</em> is a user-provided pointer used to pass external data
+to the function callbacks defined in <em>tab</em>.
+</p>
+
+<p>
+<em>tab</em> points to <em>tablen</em> caller-provided objects of type <tt>wpactrl_xchgitem_t</tt>.
+This type is a struct containing the following members:
+</p>
+
+<ul>
+ <li> <tt>char const *filter</tt>&nbsp;: a string to watch for in unsolicited messages
+sent by wpa_supplicant to the attached interface. When this string is received,
+it means the call can proceed. For instance, when a scan has been requested,
+the string to watch is <tt>CTRL-EVENT-SCAN-RESULTS</tt>. </li>
+ <li> <tt>int *cb (wpactrl_t *a, char const *msg, size_t msglen, void *aux, tain_t *stamp)</tt>&nbsp;:
+A pointer to a function that will be run as a callback when a message matching the <em>filter</em>
+field is received. It will be called with the following arguments:
+ <ul>
+  <li> <em>a</em>&nbsp;: the connection handle </li>
+  <li> <em>msg</em>&nbsp;: the message from wpa_supplicant </li>
+  <li> <em>msglen</em>&nbsp;: the size of the message </li>
+  <li> <em>aux</em>&nbsp;: the <em>aux</em> pointer provided to this <tt>wpactrl_xchg_init()</tt> call </li>
+  <li> <em>stamp</em>&nbsp;: a pointer to the current time (at the time of the callback) </li>
+ </ul> </li>
+</ul>
+
+<p>
+The <em>*cb</em> function must return 0 (and set errno) if it fails, or a
+positive integer if it succeeds. The objects in <em>tab</em> will be used
+sequentially: first a message with <em>dt&rarr;tab[0].filter</em> will
+be waited for, then <em>*dt&rarr;tab[0].cb</em> will be run; if it
+succeeds, a message with <em>dt&rarr;tab[1].filter</em> will be waited for,
+and so on. The last function, <em>*dt&rarr;tab[tablen-1].cb</em>, should
+write the final result of the whole to a place accessible by the
+user; this is one of the uses for the <em>aux</em> pointer.
+</p>
+
+<p>
+<em>limit</em> is a deadline: an absolute date after which the whole series of
+exchanges with wpa_supplicant will stop and be considered failed, i.e.
+<a href="//skarnet.org/software/skalibs/libstddjb/iopause.html">iopause</a>
+will report a timeout and <tt>wpactrl_xchg_timeout()</tt> called on
+<em>dt</em> will return 1.
+</p>
+
+<p>
+<code> int wpactrl_xchg_start (wpactrl_t *a, wpactrl_xchg_t *dt) </code> <br />
+Starts the exchange defined in the object pointed to by <em>dt</em>, with the
+wpa_supplicant instance defined by the handle <em>a</em>. Returns 1 if it
+succeeds and 0 if it fails.
+</p>
+
+<p>
+<code> void wpactrl_xchg_computedeadline (wpactrl_xchg_t const *dt, tain_t *deadline) </code> <br />
+Updates the deadline pointed to by <em>deadline</em>, destined to be used in the next
+<a href="//skarnet.org/software/skalibs/libstddjb/iopause.html">iopause</a> invocation,
+with the one contained in <em>*dt</em>. Namely: if the deadline defined by <em>*dt</em>
+is earlier than <em>*deadline</em>, replaces the latter with the former.
+</p>
+
+<p>
+<code> int wpactrl_xchg_timeout (wpactrl_t *a, wpactrl_xchg_t *dt, tain_t const *stamp) </code> <br />
+To be called after an <tt>iopause</tt> invocation that returned 0.
+Tests whether the exchange
+defined by <em>dt</em> has timed out. Returns 1 (and cleans up the relevant
+filters in <em>a</em> if it is the case, and 0 otherwise. <em>stamp</em> must
+point to the current time.
+</p>
 
 <p>
- The following functions can be used when performing calls to wpa_supplicant
-such as <tt>SCAN</tt> that answer with an asynchronous message on the "attached"
-interface.
+<code> int wpactrl_xchg_event (wpactrl_t *a, wpactrl_xchg_t *dt, tain_t *stamp) </code> <br />
+To be called after an <tt>iopause</tt> invocation that returned a positive number, and
+after a <tt>wpactrl_update(<em>a</em>) invocation.
+Advances the exchange described in <em>*dt</em>, if applicable: if a message arrived
+that matches the current filter set up by <em>*dt</em>, executes the corresponding
+callback, then sets up the next filter. <em>stamp</em> must point to the current
+time.
 </p>
 
 <p>
-</code> int wpactrl_xchg_init (wpactrl_xchg_t *dt, wpactrl_xchgitem_t const *tab, unsigned int n, tain_t const *limit, void *aux) </code> </br>
-Initializes.
+ The function returns a negative number if an error occurred and the exchange needs
+to be cancelled and freed; 0 if the exchange isn't over yet; and a positive number
+if the exchange completed successfully. Namely:
 </p>
 
+<ul>
+ <li> -2: a callback was run and returned an error. </li>
+ <li> -1: an error occurred during execution of <tt>wpactrl_xchg_event()</tt>.
+ <li> 0: either the message from wpa_supplicant that <em>*dt</em> is expecting
+has not arrived yet, or it has arrived, the relevant callback has been run and
+has succeeded, and it was not the last part of the exchange - <em>*dt</em> is
+now waiting for another message. </li>
+ <li> 1: the exchange completed successfully. The last callback should have
+written the results to the auxiliary pointer. <em>dt</em> can now be ignored. </li>
+ <li> 2: the exchange already completed in a previous invocation of
+<tt>wpactrl_xchg_event()</tt>. It's still a success, but likely signals a programming
+error. </li>
+</ul>
+
 </body>
 </html>
diff --git a/package/deps.mak b/package/deps.mak
index 79c1b29..252f4b3 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -27,16 +27,18 @@ src/libwpactrl/wpactrl_scan_parse.o src/libwpactrl/wpactrl_scan_parse.lo: src/li
 src/libwpactrl/wpactrl_selectnetwork.o src/libwpactrl/wpactrl_selectnetwork.lo: src/libwpactrl/wpactrl_selectnetwork.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_setnetworkoption.o src/libwpactrl/wpactrl_setnetworkoption.lo: src/libwpactrl/wpactrl_setnetworkoption.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_start.o src/libwpactrl/wpactrl_start.lo: src/libwpactrl/wpactrl_start.c src/include/bcnm/wpactrl.h src/libwpactrl/wpactrl-internal.h
+src/libwpactrl/wpactrl_startscan.o src/libwpactrl/wpactrl_startscan.lo: src/libwpactrl/wpactrl_startscan.c src/include/bcnm/wpactrl.h src/libwpactrl/wpactrl-internal.h
 src/libwpactrl/wpactrl_update.o src/libwpactrl/wpactrl_update.lo: src/libwpactrl/wpactrl_update.c src/include/bcnm/wpactrl.h src/libwpactrl/wpactrl-internal.h
+src/libwpactrl/wpactrl_xchg_cbres_free.o src/libwpactrl/wpactrl_xchg_cbres_free.lo: src/libwpactrl/wpactrl_xchg_cbres_free.c src/include/bcnm/wpactrl.h
+src/libwpactrl/wpactrl_xchg_cbres_zero.o src/libwpactrl/wpactrl_xchg_cbres_zero.lo: src/libwpactrl/wpactrl_xchg_cbres_zero.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_xchg_computedeadline.o src/libwpactrl/wpactrl_xchg_computedeadline.lo: src/libwpactrl/wpactrl_xchg_computedeadline.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_xchg_event.o src/libwpactrl/wpactrl_xchg_event.lo: src/libwpactrl/wpactrl_xchg_event.c src/include/bcnm/wpactrl.h
-src/libwpactrl/wpactrl_xchg_free.o src/libwpactrl/wpactrl_xchg_free.lo: src/libwpactrl/wpactrl_xchg_free.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_xchg_init.o src/libwpactrl/wpactrl_xchg_init.lo: src/libwpactrl/wpactrl_xchg_init.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_xchg_start.o src/libwpactrl/wpactrl_xchg_start.lo: src/libwpactrl/wpactrl_xchg_start.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_xchg_timeout.o src/libwpactrl/wpactrl_xchg_timeout.lo: src/libwpactrl/wpactrl_xchg_timeout.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_xchg_zero.o src/libwpactrl/wpactrl_xchg_zero.lo: src/libwpactrl/wpactrl_xchg_zero.c src/include/bcnm/wpactrl.h
 src/libwpactrl/wpactrl_zero.o src/libwpactrl/wpactrl_zero.lo: src/libwpactrl/wpactrl_zero.c src/include/bcnm/wpactrl.h
 
-libwpactrl.a.xyzzy: src/libwpactrl/wpactrl_ackmsg.o src/libwpactrl/wpactrl_addnetwork.o src/libwpactrl/wpactrl_associate.o src/libwpactrl/wpactrl_bssid_scan.o src/libwpactrl/wpactrl_command.o src/libwpactrl/wpactrl_end.o src/libwpactrl/wpactrl_env_parse.o src/libwpactrl/wpactrl_fd_recv.o src/libwpactrl/wpactrl_fd_timed_recv.o src/libwpactrl/wpactrl_filter_add.o src/libwpactrl/wpactrl_filter_exact_search.o src/libwpactrl/wpactrl_filter_match.o src/libwpactrl/wpactrl_filter_remove.o src/libwpactrl/wpactrl_findnetwork.o src/libwpactrl/wpactrl_flags_scan.o src/libwpactrl/wpactrl_msg.o src/libwpactrl/wpactrl_networks_parse.o src/libwpactrl/wpactrl_query.o src/libwpactrl/wpactrl_querysa.o src/libwpactrl/wpactrl_removenetwork.o src/libwpactrl/wpactrl_scan_parse.o src/libwpactrl/wpactrl_selectnetwork.o src/libwpactrl/wpactrl_setnetworkoption.o src/libwpactrl/wpactrl_start.o src/libwpactrl/wpactrl_update.o src/libwpactrl/wpactrl_xchg_computedeadline.o src/libwpactrl/wpactrl_xchg_event.o src/libwpactrl/wpactrl_xchg_free.o src/libwpactrl/wpactrl_xchg_init.o src/libwpactrl/wpactrl_xchg_start.o src/libwpactrl/wpactrl_xchg_timeout.o src/libwpactrl/wpactrl_xchg_zero.o src/libwpactrl/wpactrl_zero.o
+libwpactrl.a.xyzzy: src/libwpactrl/wpactrl_ackmsg.o src/libwpactrl/wpactrl_addnetwork.o src/libwpactrl/wpactrl_associate.o src/libwpactrl/wpactrl_bssid_scan.o src/libwpactrl/wpactrl_command.o src/libwpactrl/wpactrl_end.o src/libwpactrl/wpactrl_env_parse.o src/libwpactrl/wpactrl_fd_recv.o src/libwpactrl/wpactrl_fd_timed_recv.o src/libwpactrl/wpactrl_filter_add.o src/libwpactrl/wpactrl_filter_exact_search.o src/libwpactrl/wpactrl_filter_match.o src/libwpactrl/wpactrl_filter_remove.o src/libwpactrl/wpactrl_findnetwork.o src/libwpactrl/wpactrl_flags_scan.o src/libwpactrl/wpactrl_msg.o src/libwpactrl/wpactrl_networks_parse.o src/libwpactrl/wpactrl_query.o src/libwpactrl/wpactrl_querysa.o src/libwpactrl/wpactrl_removenetwork.o src/libwpactrl/wpactrl_scan_parse.o src/libwpactrl/wpactrl_selectnetwork.o src/libwpactrl/wpactrl_setnetworkoption.o src/libwpactrl/wpactrl_start.o src/libwpactrl/wpactrl_startscan.o src/libwpactrl/wpactrl_update.o src/libwpactrl/wpactrl_xchg_cbres_free.o src/libwpactrl/wpactrl_xchg_cbres_zero.o src/libwpactrl/wpactrl_xchg_computedeadline.o src/libwpactrl/wpactrl_xchg_event.o src/libwpactrl/wpactrl_xchg_init.o src/libwpactrl/wpactrl_xchg_start.o src/libwpactrl/wpactrl_xchg_timeout.o src/libwpactrl/wpactrl_xchg_zero.o src/libwpactrl/wpactrl_zero.o
 libwpactrl.so.xyzzy: EXTRA_LIBS :=
-libwpactrl.so.xyzzy: src/libwpactrl/wpactrl_ackmsg.lo src/libwpactrl/wpactrl_addnetwork.lo src/libwpactrl/wpactrl_associate.lo src/libwpactrl/wpactrl_bssid_scan.lo src/libwpactrl/wpactrl_command.lo src/libwpactrl/wpactrl_end.lo src/libwpactrl/wpactrl_env_parse.lo src/libwpactrl/wpactrl_fd_recv.lo src/libwpactrl/wpactrl_fd_timed_recv.lo src/libwpactrl/wpactrl_filter_add.lo src/libwpactrl/wpactrl_filter_exact_search.lo src/libwpactrl/wpactrl_filter_match.lo src/libwpactrl/wpactrl_filter_remove.lo src/libwpactrl/wpactrl_findnetwork.lo src/libwpactrl/wpactrl_flags_scan.lo src/libwpactrl/wpactrl_msg.lo src/libwpactrl/wpactrl_networks_parse.lo src/libwpactrl/wpactrl_query.lo src/libwpactrl/wpactrl_querysa.lo src/libwpactrl/wpactrl_removenetwork.lo src/libwpactrl/wpactrl_scan_parse.lo src/libwpactrl/wpactrl_selectnetwork.lo src/libwpactrl/wpactrl_setnetworkoption.lo src/libwpactrl/wpactrl_start.lo src/libwpactrl/wpactrl_update.lo src/libwpactrl/wpactrl_xchg_computedeadline.lo src/libwpactrl/wpactrl_xchg_event.lo src/libwpactrl/wpactrl_xchg_free.lo src/libwpactrl/wpactrl_xchg_init.lo src/libwpactrl/wpactrl_xchg_start.lo src/libwpactrl/wpactrl_xchg_timeout.lo src/libwpactrl/wpactrl_xchg_zero.lo src/libwpactrl/wpactrl_zero.lo
+libwpactrl.so.xyzzy: src/libwpactrl/wpactrl_ackmsg.lo src/libwpactrl/wpactrl_addnetwork.lo src/libwpactrl/wpactrl_associate.lo src/libwpactrl/wpactrl_bssid_scan.lo src/libwpactrl/wpactrl_command.lo src/libwpactrl/wpactrl_end.lo src/libwpactrl/wpactrl_env_parse.lo src/libwpactrl/wpactrl_fd_recv.lo src/libwpactrl/wpactrl_fd_timed_recv.lo src/libwpactrl/wpactrl_filter_add.lo src/libwpactrl/wpactrl_filter_exact_search.lo src/libwpactrl/wpactrl_filter_match.lo src/libwpactrl/wpactrl_filter_remove.lo src/libwpactrl/wpactrl_findnetwork.lo src/libwpactrl/wpactrl_flags_scan.lo src/libwpactrl/wpactrl_msg.lo src/libwpactrl/wpactrl_networks_parse.lo src/libwpactrl/wpactrl_query.lo src/libwpactrl/wpactrl_querysa.lo src/libwpactrl/wpactrl_removenetwork.lo src/libwpactrl/wpactrl_scan_parse.lo src/libwpactrl/wpactrl_selectnetwork.lo src/libwpactrl/wpactrl_setnetworkoption.lo src/libwpactrl/wpactrl_start.lo src/libwpactrl/wpactrl_startscan.lo src/libwpactrl/wpactrl_update.lo src/libwpactrl/wpactrl_xchg_cbres_free.lo src/libwpactrl/wpactrl_xchg_cbres_zero.lo src/libwpactrl/wpactrl_xchg_computedeadline.lo src/libwpactrl/wpactrl_xchg_event.lo src/libwpactrl/wpactrl_xchg_init.lo src/libwpactrl/wpactrl_xchg_start.lo src/libwpactrl/wpactrl_xchg_timeout.lo src/libwpactrl/wpactrl_xchg_zero.lo src/libwpactrl/wpactrl_zero.lo
diff --git a/src/include/bcnm/wpactrl.h b/src/include/bcnm/wpactrl.h
index 874a14f..7c31b22 100644
--- a/src/include/bcnm/wpactrl.h
+++ b/src/include/bcnm/wpactrl.h
@@ -68,6 +68,7 @@ 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)
 
+#define wpactrl_fd(a) ((a)->fda)
 extern int wpactrl_update (wpactrl_t *) ;
 extern char *wpactrl_msg (wpactrl_t *) gccattr_pure ;
 extern void wpactrl_ackmsg (wpactrl_t *) ;
@@ -118,13 +119,12 @@ 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 ;
+  wpactrl_xchg_func_t_ref cb ;
 } ;
 
 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 ;
@@ -132,20 +132,30 @@ struct wpactrl_xchg_s
   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 }
+#define WPACTRL_XCHG_ZERO { .tab = 0, .n = 0, .i = 0, .deadline = TAIN_ZERO, .status = ECONNABORTED, .aux = 0 }
+#define WPACTRL_XCHG_INIT(array, size, limit, extra) { .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 void 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_timeout (wpactrl_t *, wpactrl_xchg_t *, tain_t const *) ;
+#define wpactrl_xchg_timeout_g(a, dt) wpactrl_xchg_timeout(a, (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)
 
+typedef struct wpactrl_xchg_cbres_s wpactrl_xchg_cbres_t, *wpactrl_xchg_cbres_t_ref ;
+struct wpactrl_xchg_cbres_s
+{
+  genalloc parsed ;
+  stralloc storage ;
+} ;
+#define WPACTRL_XCHG_CBRES_ZERO { .parsed = GENALLOC_ZERO, .storage = STRALLOC_ZERO }
+
+extern wpactrl_xchg_cbres_t const wpactrl_xchg_cbres_zero ;
+extern void wpactrl_xchg_cbres_free (wpactrl_xchg_cbres_t *) ;
+
 
  /* High-level functions for common calls to wpa_supplicant */
 
@@ -167,5 +177,7 @@ extern wparesponse_t wpactrl_selectnetwork (wpactrl_t *, uint32_t, tain_t *) ;
 extern int wpactrl_associate (wpactrl_t *, char const *, char const *, tain_t *) ;
 #define wpactrl_associate_g(a, ssid, psk) wpactrl_associate(a, ssid, (psk), &STAMP)
 
+extern int wpactrl_startscan (wpactrl_t *, wpactrl_xchg_t *, wpactrl_xchg_cbres_t *, tain_t const *, tain_t *) ;
+#define wpactrl_startscan_g(a, xchg, res, limit) wpactrl_startscan(a, xchg, res, (limit), &STAMP)
 
 #endif
diff --git a/src/libwpactrl/deps-lib/wpactrl b/src/libwpactrl/deps-lib/wpactrl
index 74d579a..d1e7a2b 100644
--- a/src/libwpactrl/deps-lib/wpactrl
+++ b/src/libwpactrl/deps-lib/wpactrl
@@ -22,10 +22,12 @@ wpactrl_scan_parse.o
 wpactrl_selectnetwork.o
 wpactrl_setnetworkoption.o
 wpactrl_start.o
+wpactrl_startscan.o
 wpactrl_update.o
+wpactrl_xchg_cbres_free.o
+wpactrl_xchg_cbres_zero.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
diff --git a/src/libwpactrl/wpactrl_filter_match.c b/src/libwpactrl/wpactrl_filter_match.c
index 30ed0ff..09b6eab 100644
--- a/src/libwpactrl/wpactrl_filter_match.c
+++ b/src/libwpactrl/wpactrl_filter_match.c
@@ -7,6 +7,8 @@ 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 ;
+  if (len < 4) return 0 ;
+  s += 3 ; len -= 3 ;
   while (filterlen)
   {
     size_t flen = strlen(filters) ;
diff --git a/src/libwpactrl/wpactrl_startscan.c b/src/libwpactrl/wpactrl_startscan.c
new file mode 100644
index 0000000..7ce5f52
--- /dev/null
+++ b/src/libwpactrl/wpactrl_startscan.c
@@ -0,0 +1,30 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <bcnm/wpactrl.h>
+#include "wpactrl-internal.h"
+
+static int wpactrl_scan_cb (wpactrl_t *a, char const *s, size_t len, void *aux, tain_t *stamp)
+{
+  wpactrl_xchg_cbres_t *res = aux ;
+  char buf[WPACTRL_PACKET_MAX] ;
+  ssize_t r = wpactrl_query(a, "SCAN_RESULTS", buf, WPACTRL_PACKET_MAX, stamp) ;
+  if (r <= 0) return 0 ;
+  (void)s ;
+  (void)len ;
+  return wpactrl_scan_parse(buf, r, &res->parsed, &res->storage) ;
+}
+
+static wpactrl_xchgitem_t wpactrl_xchgitem_scan =
+{
+  .filter = "CTRL-EVENT-SCAN-RESULTS",
+  .cb = &wpactrl_scan_cb
+} ;
+
+int wpactrl_startscan (wpactrl_t *a, wpactrl_xchg_t *xchg, wpactrl_xchg_cbres_t *res, tain_t const *deadline, tain_t *stamp)
+{
+  wparesponse_t r = wpactrl_command(a, "SCAN", stamp) ;
+  if (r != WPA_OK && r != WPA_FAILBUSY) return (errno = EIO, 0) ;
+  wpactrl_xchg_init(xchg, &wpactrl_xchgitem_scan, 1, deadline, res) ;
+  return wpactrl_xchg_start(a, xchg) ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_cbres_free.c b/src/libwpactrl/wpactrl_xchg_cbres_free.c
new file mode 100644
index 0000000..b12a3fc
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_cbres_free.c
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <bcnm/wpactrl.h>
+
+void wpactrl_xchg_cbres_free (wpactrl_xchg_cbres_t *cr)
+{
+  genalloc_free(int, &cr->parsed) ; /* relies on genericity of genalloc_free */
+  stralloc_free(&cr->storage) ;
+}
diff --git a/src/libwpactrl/wpactrl_xchg_cbres_zero.c b/src/libwpactrl/wpactrl_xchg_cbres_zero.c
new file mode 100644
index 0000000..c38dc33
--- /dev/null
+++ b/src/libwpactrl/wpactrl_xchg_cbres_zero.c
@@ -0,0 +1,5 @@
+/* ISC license. */
+
+#include <bcnm/wpactrl.h>
+
+wpactrl_xchg_cbres_t const wpactrl_xchg_cbres_zero = WPACTRL_XCHG_CBRES_ZERO ;
diff --git a/src/libwpactrl/wpactrl_xchg_event.c b/src/libwpactrl/wpactrl_xchg_event.c
index 6d25295..e9d2f06 100644
--- a/src/libwpactrl/wpactrl_xchg_event.c
+++ b/src/libwpactrl/wpactrl_xchg_event.c
@@ -1,6 +1,8 @@
 /* ISC license. */
 
 #include <string.h>
+#include <errno.h>
+#include <skalibs/error.h>
 #include <skalibs/stralloc.h>
 #include <bcnm/wpactrl.h>
 
@@ -10,7 +12,7 @@ static inline size_t wpactrl_findmsg (wpactrl_t *a, char const *filter)
   size_t i = 0 ;
   while (i < a->data.len)
   {
-    if (!strncmp(a->data.s + i, filter, filterlen)) break ;
+    if (!strncmp(a->data.s + i + 3, filter, filterlen)) break ;
     i += strlen(a->data.s + i) + 1 ;
   }
   return i ;
@@ -20,23 +22,19 @@ 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 ;
+  if (!error_isagain(dt->status)) return (errno = EINVAL, -1) ;
   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 (!(*dt->tab[dt->i].cb)(a, a->data.s + pos, len - 1, dt->aux, stamp)) return -2 ;
+  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 (++dt->i == dt->n)
   {
-    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 ;
+  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
deleted file mode 100644
index e244a52..0000000
--- a/src/libwpactrl/wpactrl_xchg_free.c
+++ /dev/null
@@ -1,10 +0,0 @@
-/* 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
index 2e2f391..b9d35f0 100644
--- a/src/libwpactrl/wpactrl_xchg_init.c
+++ b/src/libwpactrl/wpactrl_xchg_init.c
@@ -1,12 +1,10 @@
 /* ISC license. */
 
 #include <errno.h>
-#include <skalibs/stralloc.h>
 #include <bcnm/wpactrl.h>
 
 void 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 ;
diff --git a/src/libwpactrl/wpactrl_xchg_timeout.c b/src/libwpactrl/wpactrl_xchg_timeout.c
index 01907ea..fd99390 100644
--- a/src/libwpactrl/wpactrl_xchg_timeout.c
+++ b/src/libwpactrl/wpactrl_xchg_timeout.c
@@ -4,11 +4,12 @@
 #include <skalibs/tai.h>
 #include <bcnm/wpactrl.h>
 
-int wpactrl_xchg_timeout (wpactrl_xchg_t *dt, tain_t const *stamp)
+int wpactrl_xchg_timeout (wpactrl_t *a, wpactrl_xchg_t *dt, tain_t const *stamp)
 {
   if (!tain_less(stamp, &dt->deadline))
   {
     dt->status = ETIMEDOUT ;
+    wpactrl_filter_remove(a, dt->tab[dt->i].filter) ;
     return 1 ;
   }
   else return 0 ;