summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2014-02-05 15:36:51 +0100
committerDavid Herrmann <dh.herrmann@gmail.com>2014-02-05 15:36:51 +0100
commit3a17743bd82dad3400c44b7811c0fadac4c2828c (patch)
tree1636cca15cf2d4a40114a17a330f26372ce57eb3
parent6d57a6a0d910d78c1279a9463ca2bae7c6a6cdfb (diff)
Add wfd_wpa_* API
As wpa_supplicant is the de-facto standard for wifi on linux, add a common API to deal with it. It's mostly copied from old openwfd so should be working fine. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am12
-rw-r--r--docs/libwfd.sym19
-rw-r--r--src/libwfd.h144
-rw-r--r--src/wpa_ctrl.c794
-rw-r--r--src/wpa_parser.c537
-rw-r--r--test/test_wpa.c168
7 files changed, 1673 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 53d0475..dba6dfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ m4/
stamp-h1
test-suite.log
test_rtsp
+test_wpa
diff --git a/Makefile.am b/Makefile.am
index f2a1b82..6aa848d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -98,7 +98,9 @@ pkgconfig_DATA += docs/libwfd.pc
libwfd_la_SOURCES = \
src/libwfd.h \
src/rtsp_decoder.c \
- src/rtsp_tokenizer.c
+ src/rtsp_tokenizer.c \
+ src/wpa_ctrl.c \
+ src/wpa_parser.c
libwfd_la_CPPFLAGS = $(AM_CPPFLAGS)
libwfd_la_LIBADD = libshl.la
EXTRA_libwfd_la_DEPENDENCIES = $(top_srcdir)/docs/libwfd.sym
@@ -112,7 +114,8 @@ libwfd_la_LDFLAGS = \
#
tests = \
- test_rtsp
+ test_rtsp \
+ test_wpa
if BUILD_HAVE_CHECK
check_PROGRAMS += $(tests)
@@ -136,6 +139,11 @@ test_rtsp_CPPFLAGS = $(test_cflags)
test_rtsp_LDADD = $(test_libs)
test_rtsp_LDFLAGS = $(test_lflags)
+test_wpa_SOURCES = test/test_wpa.c $(test_sources)
+test_wpa_CPPFLAGS = $(test_cflags)
+test_wpa_LDADD = $(test_libs)
+test_wpa_LDFLAGS = $(test_lflags)
+
#
# Phony targets
#
diff --git a/docs/libwfd.sym b/docs/libwfd.sym
index a29cda1..352168b 100644
--- a/docs/libwfd.sym
+++ b/docs/libwfd.sym
@@ -17,6 +17,25 @@ global:
wfd_rtsp_decoder_set_data;
wfd_rtsp_decoder_get_data;
wfd_rtsp_decoder_feed;
+
+ wfd_wpa_ctrl_new;
+ wfd_wpa_ctrl_ref;
+ wfd_wpa_ctrl_unref;
+ wfd_wpa_ctrl_set_data;
+ wfd_wpa_ctrl_get_data;
+ wfd_wpa_ctrl_open;
+ wfd_wpa_ctrl_close;
+ wfd_wpa_ctrl_is_open;
+ wfd_wpa_ctrl_get_fd;
+ wfd_wpa_ctrl_set_sigmask;
+ wfd_wpa_ctrl_dispatch;
+ wfd_wpa_ctrl_request;
+ wfd_wpa_ctrl_request_ok;
+
+ wfd_wpa_event_init;
+ wfd_wpa_event_reset;
+ wfd_wpa_event_parse;
+ wfd_wpa_event_name;
local:
*;
};
diff --git a/src/libwfd.h b/src/libwfd.h
index a43dd66..834e4f1 100644
--- a/src/libwfd.h
+++ b/src/libwfd.h
@@ -511,6 +511,150 @@ struct wfd_ie_sub_alt_mac {
/** @} */
/**
+ * @defgroup wfd_wpa WPA-Supplicant API
+ * Helper API to deal with wpa_supplicant for WFD devices
+ *
+ * On linux wpa_supplicant is the de-facto standard for wifi-handling. It
+ * provides a standard-compiant supplicant implementation with a custom API for
+ * applications. This API implements helpers to deal with this daemon and get
+ * wifi-P2P connections for WFD working.
+ *
+ * @{
+ */
+
+/* wpa ctrl */
+
+struct wfd_wpa_ctrl;
+
+typedef void (*wfd_wpa_ctrl_event_t) (struct wfd_wpa_ctrl *wpa, void *data,
+ void *buf, size_t len);
+
+int wfd_wpa_ctrl_new(wfd_wpa_ctrl_event_t event_fn, void *data,
+ struct wfd_wpa_ctrl **out);
+void wfd_wpa_ctrl_ref(struct wfd_wpa_ctrl *wpa);
+void wfd_wpa_ctrl_unref(struct wfd_wpa_ctrl *wpa);
+
+void wfd_wpa_ctrl_set_data(struct wfd_wpa_ctrl *wpa, void *data);
+void *wfd_wpa_ctrl_get_data(struct wfd_wpa_ctrl *wpa);
+
+int wfd_wpa_ctrl_open(struct wfd_wpa_ctrl *wpa, const char *ctrl_path);
+void wfd_wpa_ctrl_close(struct wfd_wpa_ctrl *wpa);
+bool wfd_wpa_ctrl_is_open(struct wfd_wpa_ctrl *wpa);
+
+int wfd_wpa_ctrl_get_fd(struct wfd_wpa_ctrl *wpa);
+void wfd_wpa_ctrl_set_sigmask(struct wfd_wpa_ctrl *wpa, const sigset_t *mask);
+int wfd_wpa_ctrl_dispatch(struct wfd_wpa_ctrl *wpa, int timeout);
+
+int wfd_wpa_ctrl_request(struct wfd_wpa_ctrl *wpa, const void *cmd,
+ size_t cmd_len, void *reply, size_t *reply_len,
+ int timeout);
+int wfd_wpa_ctrl_request_ok(struct wfd_wpa_ctrl *wpa, const void *cmd,
+ size_t cmd_len, int timeout);
+
+/* wpa parser */
+
+enum wfd_wpa_event_type {
+ WFD_WPA_EVENT_UNKNOWN,
+ WFD_WPA_EVENT_AP_STA_CONNECTED,
+ WFD_WPA_EVENT_AP_STA_DISCONNECTED,
+ WFD_WPA_EVENT_P2P_DEVICE_FOUND,
+ WFD_WPA_EVENT_P2P_FIND_STOPPED,
+ WFD_WPA_EVENT_P2P_GO_NEG_REQUEST,
+ WFD_WPA_EVENT_P2P_GO_NEG_SUCCESS,
+ WFD_WPA_EVENT_P2P_GO_NEG_FAILURE,
+ WFD_WPA_EVENT_P2P_GROUP_FORMATION_SUCCESS,
+ WFD_WPA_EVENT_P2P_GROUP_FORMATION_FAILURE,
+ WFD_WPA_EVENT_P2P_GROUP_STARTED,
+ WFD_WPA_EVENT_P2P_GROUP_REMOVED,
+ WFD_WPA_EVENT_P2P_PROV_DISC_SHOW_PIN,
+ WFD_WPA_EVENT_P2P_PROV_DISC_ENTER_PIN,
+ WFD_WPA_EVENT_P2P_PROV_DISC_PBC_REQ,
+ WFD_WPA_EVENT_P2P_PROV_DISC_PBC_RESP,
+ WFD_WPA_EVENT_P2P_SERV_DISC_REQ,
+ WFD_WPA_EVENT_P2P_SERV_DISC_RESP,
+ WFD_WPA_EVENT_P2P_INVITATION_RECEIVED,
+ WFD_WPA_EVENT_P2P_INVITATION_RESULT,
+ WFD_WPA_EVENT_COUNT,
+};
+
+enum wfd_wpa_event_priority {
+ WFD_WPA_EVENT_P_MSGDUMP,
+ WFD_WPA_EVENT_P_DEBUG,
+ WFD_WPA_EVENT_P_INFO,
+ WFD_WPA_EVENT_P_WARNING,
+ WFD_WPA_EVENT_P_ERROR,
+ WFD_WPA_EVENT_P_COUNT
+};
+
+enum wfd_wpa_event_role {
+ WFD_WPA_EVENT_ROLE_GO,
+ WFD_WPA_EVENT_ROLE_CLIENT,
+};
+
+#define WFD_WPA_EVENT_MAC_STRLEN 18
+
+struct wfd_wpa_event {
+ unsigned int type;
+ unsigned int priority;
+ char *raw;
+
+ union wfd_wpa_event_payload {
+ struct wfd_wpa_event_ap_sta_connected {
+ char mac[WFD_WPA_EVENT_MAC_STRLEN];
+ } ap_sta_connected;
+
+ struct wfd_wpa_event_ap_sta_disconnected {
+ char mac[WFD_WPA_EVENT_MAC_STRLEN];
+ } ap_sta_disconnected;
+
+ struct wfd_wpa_event_p2p_device_found {
+ char peer_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ char *name;
+ } p2p_device_found;
+
+ struct wfd_wpa_event_p2p_go_neg_success {
+ char peer_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ unsigned int role;
+ } p2p_go_neg_success;
+
+ struct wfd_wpa_event_p2p_group_started {
+ char go_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ unsigned int role;
+ char *ifname;
+ } p2p_group_started;
+
+ struct wfd_wpa_event_p2p_group_removed {
+ unsigned int role;
+ char *ifname;
+ } p2p_group_removed;
+
+ struct wfd_wpa_event_p2p_prov_disc_show_pin {
+ char peer_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ char *pin;
+ } p2p_prov_disc_show_pin;
+
+ struct wfd_wpa_event_p2p_prov_disc_enter_pin {
+ char peer_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ } p2p_prov_disc_enter_pin;
+
+ struct wfd_wpa_event_p2p_prov_disc_pbc_req {
+ char peer_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ } p2p_prov_disc_pbc_req;
+
+ struct wfd_wpa_event_p2p_prov_disc_pbc_resp {
+ char peer_mac[WFD_WPA_EVENT_MAC_STRLEN];
+ } p2p_prov_disc_pbc_resp;
+ } p;
+};
+
+void wfd_wpa_event_init(struct wfd_wpa_event *ev);
+void wfd_wpa_event_reset(struct wfd_wpa_event *ev);
+int wfd_wpa_event_parse(struct wfd_wpa_event *ev, const char *event);
+const char *wfd_wpa_event_name(unsigned int type);
+
+/** @} */
+
+/**
* @defgroup wfd_rtsp Wifi-Display API for RTSP
* API for the Wifi-Display specification regarding RTSP
*
diff --git a/src/wpa_ctrl.c b/src/wpa_ctrl.c
new file mode 100644
index 0000000..611f539
--- /dev/null
+++ b/src/wpa_ctrl.c
@@ -0,0 +1,794 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+#include "libwfd.h"
+#include "shl_macro.h"
+
+#define CTRL_PATH_TEMPLATE "/tmp/libwfd-wpa-ctrl-%d-%lu"
+#define REQ_REPLY_MAX 512
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)0)->sun_path))
+#endif
+
+struct wfd_wpa_ctrl {
+ unsigned long ref;
+ wfd_wpa_ctrl_event_t event_fn;
+ void *data;
+ sigset_t mask;
+ int efd;
+ int tfd;
+
+ int req_fd;
+ char req_name[UNIX_PATH_MAX];
+ int ev_fd;
+ char ev_name[UNIX_PATH_MAX];
+};
+
+static int wpa_request(int fd, const void *cmd, size_t cmd_len,
+ void *reply, size_t *reply_len, int64_t *t2,
+ const sigset_t *mask);
+static int wpa_request_ok(int fd, const void *cmd, size_t cmd_len, int64_t *t,
+ const sigset_t *mask);
+
+static int64_t get_time_us(void)
+{
+ int64_t t;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ t = ts.tv_sec * 1000LL * 1000LL;
+ t += ts.tv_nsec / 1000LL;
+
+ return t;
+}
+
+static void us_to_timespec(struct timespec *ts, int64_t us)
+{
+ ts->tv_sec = us / (1000LL * 1000LL);
+ ts->tv_nsec = (us % (1000LL * 1000LL)) * 1000LL;
+}
+
+_shl_public_
+int wfd_wpa_ctrl_new(wfd_wpa_ctrl_event_t event_fn, void *data,
+ struct wfd_wpa_ctrl **out)
+{
+ struct wfd_wpa_ctrl *wpa;
+ struct epoll_event ev;
+ int r;
+
+ if (!out || !event_fn)
+ return -EINVAL;
+
+ wpa = calloc(1, sizeof(*wpa));
+ if (!wpa)
+ return -ENOMEM;
+ wpa->ref = 1;
+ wpa->event_fn = event_fn;
+ wpa->data = data;
+ wpa->efd = -1;
+ wpa->tfd = -1;
+ wpa->req_fd = -1;
+ wpa->ev_fd = -1;
+ sigemptyset(&wpa->mask);
+
+ wpa->efd = epoll_create1(EPOLL_CLOEXEC);
+ if (wpa->efd < 0) {
+ r = -errno;
+ goto err_wpa;
+ }
+
+ wpa->tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+ if (wpa->tfd < 0) {
+ r = -errno;
+ goto err_efd;
+ }
+
+ memset(&ev, 0, sizeof(ev));
+ ev.events = EPOLLHUP | EPOLLERR | EPOLLIN;
+ ev.data.ptr = &wpa->tfd;
+
+ r = epoll_ctl(wpa->efd, EPOLL_CTL_ADD, wpa->tfd, &ev);
+ if (r < 0) {
+ r = -errno;
+ goto err_tfd;
+ }
+
+ *out = wpa;
+ return 0;
+
+err_tfd:
+ close(wpa->tfd);
+err_efd:
+ close(wpa->efd);
+err_wpa:
+ free(wpa);
+ return r;
+}
+
+_shl_public_
+void wfd_wpa_ctrl_ref(struct wfd_wpa_ctrl *wpa)
+{
+ if (!wpa || !wpa->ref)
+ return;
+
+ ++wpa->ref;
+}
+
+_shl_public_
+void wfd_wpa_ctrl_unref(struct wfd_wpa_ctrl *wpa)
+{
+ if (!wpa || !wpa->ref || --wpa->ref)
+ return;
+
+ wfd_wpa_ctrl_close(wpa);
+ close(wpa->tfd);
+ close(wpa->efd);
+ free(wpa);
+}
+
+_shl_public_
+void wfd_wpa_ctrl_set_data(struct wfd_wpa_ctrl *wpa, void *data)
+{
+ if (!wpa)
+ return;
+
+ wpa->data = data;
+}
+
+_shl_public_
+void *wfd_wpa_ctrl_get_data(struct wfd_wpa_ctrl *wpa)
+{
+ if (!wpa)
+ return NULL;
+
+ return wpa->data;
+}
+
+static int bind_socket(int fd, char *name)
+{
+ static unsigned long real_counter;
+ unsigned long counter;
+ struct sockaddr_un src;
+ int r;
+ bool tried = false;
+
+ /* TODO: make wpa_supplicant allow unbound clients */
+
+ /* Yes, this counter is racy, but wpa_supplicant doesn't provide support
+ * for unbound clients (it crashes..). We could add a current-time based
+ * random part, but that might leave stupid pipes around in /tmp. So
+ * lets just use this internal counter and blame
+ * wpa_supplicant.. Yey! */
+ counter = real_counter++;
+
+ memset(&src, 0, sizeof(src));
+ src.sun_family = AF_UNIX;
+
+ /* Ugh! mktemp() is racy but stupid wpa_supplicant requires us to bind
+ * to a real file. Older versions will segfault if we don't... */
+ snprintf(name, UNIX_PATH_MAX - 1, CTRL_PATH_TEMPLATE,
+ (int)getpid(), counter);
+ name[UNIX_PATH_MAX - 1] = 0;
+
+try_again:
+ strcpy(src.sun_path, name);
+
+ r = bind(fd, (struct sockaddr*)&src, sizeof(src));
+ if (r < 0) {
+ if (errno == EADDRINUSE && !tried) {
+ tried = true;
+ unlink(name);
+ goto try_again;
+ }
+
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int connect_socket(int fd, const char *ctrl_path)
+{
+ int r;
+ struct sockaddr_un dst;
+ size_t len;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sun_family = AF_UNIX;
+
+ len = strlen(ctrl_path);
+ if (!strncmp(ctrl_path, "@abstract:", 10)) {
+ if (len > sizeof(dst.sun_path) - 2)
+ return -EINVAL;
+
+ dst.sun_path[0] = 0;
+ dst.sun_path[sizeof(dst.sun_path) - 1] = 0;
+ strncpy(&dst.sun_path[1], &ctrl_path[10],
+ sizeof(dst.sun_path) - 2);
+ } else {
+ if (len > sizeof(dst.sun_path) - 1)
+ return -EINVAL;
+
+ dst.sun_path[sizeof(dst.sun_path) - 1] = 0;
+ strncpy(dst.sun_path, ctrl_path, sizeof(dst.sun_path) - 1);
+ }
+
+ r = connect(fd, (struct sockaddr*)&dst, sizeof(dst));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int open_socket(struct wfd_wpa_ctrl *wpa, const char *ctrl_path,
+ void *data, char *name)
+{
+ int fd, r;
+ struct epoll_event ev;
+
+ fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -errno;
+
+ r = bind_socket(fd, name);
+ if (r < 0)
+ goto err_fd;
+
+ r = connect_socket(fd, ctrl_path);
+ if (r < 0)
+ goto err_name;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.events = EPOLLHUP | EPOLLERR | EPOLLIN;
+ ev.data.ptr = data;
+ r = epoll_ctl(wpa->efd, EPOLL_CTL_ADD, fd, &ev);
+ if (r < 0) {
+ r = -errno;
+ goto err_name;
+ }
+
+ return fd;
+
+err_name:
+ unlink(name);
+err_fd:
+ close(fd);
+ return r;
+}
+
+static void close_socket(struct wfd_wpa_ctrl *wpa, int fd, char *name)
+{
+ epoll_ctl(wpa->efd, EPOLL_CTL_DEL, fd, NULL);
+ unlink(name);
+ close(fd);
+}
+
+static int arm_timer(struct wfd_wpa_ctrl *wpa, int64_t usecs)
+{
+ struct itimerspec spec;
+ int r;
+
+ us_to_timespec(&spec.it_value, usecs);
+ spec.it_interval = spec.it_value;
+
+ r = timerfd_settime(wpa->tfd, 0, &spec, NULL);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+static void disarm_timer(struct wfd_wpa_ctrl *wpa)
+{
+ arm_timer(wpa, 0);
+}
+
+_shl_public_
+int wfd_wpa_ctrl_open(struct wfd_wpa_ctrl *wpa, const char *ctrl_path)
+{
+ int r;
+ int64_t t;
+
+ if (!wpa || !ctrl_path)
+ return -EINVAL;
+ if (wfd_wpa_ctrl_is_open(wpa))
+ return -EALREADY;
+
+ /* 10s PING timer for timeouts */
+ r = arm_timer(wpa, 10000000LL);
+ if (r < 0)
+ return r;
+
+ wpa->req_fd = open_socket(wpa, ctrl_path, &wpa->req_fd, wpa->req_name);
+ if (wpa->req_fd < 0) {
+ r = wpa->req_fd;
+ goto err_timer;
+ }
+
+ wpa->ev_fd = open_socket(wpa, ctrl_path, &wpa->ev_fd, wpa->ev_name);
+ if (wpa->ev_fd < 0) {
+ r = wpa->ev_fd;
+ goto err_req;
+ }
+
+ r = wpa_request_ok(wpa->ev_fd, "ATTACH", 6, NULL, &wpa->mask);
+ if (r < 0)
+ goto err_ev;
+
+ return 0;
+
+err_ev:
+ t = 0;
+ wpa_request(wpa->ev_fd, "DETACH", 6, NULL, NULL, &t, &wpa->mask);
+ close_socket(wpa, wpa->ev_fd, wpa->ev_name);
+ wpa->ev_fd = -1;
+err_req:
+ close_socket(wpa, wpa->req_fd, wpa->req_name);
+ wpa->req_fd = -1;
+err_timer:
+ disarm_timer(wpa);
+ return r;
+}
+
+_shl_public_
+void wfd_wpa_ctrl_close(struct wfd_wpa_ctrl *wpa)
+{
+ int64_t t;
+
+ if (!wpa || !wfd_wpa_ctrl_is_open(wpa))
+ return;
+
+ t = 0;
+ wpa_request(wpa->ev_fd, "DETACH", 6, NULL, NULL, &t, &wpa->mask);
+
+ close_socket(wpa, wpa->ev_fd, wpa->ev_name);
+ wpa->ev_fd = -1;
+
+ close_socket(wpa, wpa->req_fd, wpa->req_name);
+ wpa->req_fd = -1;
+
+ disarm_timer(wpa);
+}
+
+_shl_public_
+bool wfd_wpa_ctrl_is_open(struct wfd_wpa_ctrl *wpa)
+{
+ return wpa && wpa->ev_fd >= 0;
+}
+
+_shl_public_
+int wfd_wpa_ctrl_get_fd(struct wfd_wpa_ctrl *wpa)
+{
+ return wpa ? wpa->efd : -1;
+}
+
+_shl_public_
+void wfd_wpa_ctrl_set_sigmask(struct wfd_wpa_ctrl *wpa, const sigset_t *mask)
+{
+ if (!wpa || !mask)
+ return;
+
+ memcpy(&wpa->mask, mask, sizeof(sigset_t));
+}
+
+static int read_ev(struct wfd_wpa_ctrl *wpa)
+{
+ char buf[REQ_REPLY_MAX + 1];
+ ssize_t l;
+
+ do {
+ l = recv(wpa->ev_fd, buf, sizeof(buf) - 1, MSG_DONTWAIT);
+ if (l < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+ else
+ return -errno;
+ } else if (l > 0) {
+ if (l > sizeof(buf) - 1)
+ l = sizeof(buf) - 1;
+ buf[l] = 0;
+
+ /* only handle event-msgs ('<') on ev-socket */
+ if (*buf == '<')
+ wpa->event_fn(wpa, wpa->data, buf, l);
+
+ /* exit if the callback closed the connection */
+ if (!wfd_wpa_ctrl_is_open(wpa))
+ return -ENODEV;
+ }
+ } while (l > 0);
+
+ return 0;
+}
+
+static int dispatch_ev(struct wfd_wpa_ctrl *wpa, const struct epoll_event *e)
+{
+ int r;
+
+ if (e->events & EPOLLIN) {
+ r = read_ev(wpa);
+ if (r < 0)
+ return r;
+ }
+
+ /* handle HUP/ERR last so we drain input first */
+ if (e->events & (EPOLLHUP | EPOLLERR))
+ return -EPIPE;
+
+ return 0;
+}
+
+static int read_req(struct wfd_wpa_ctrl *wpa)
+{
+ char buf[REQ_REPLY_MAX];
+ ssize_t l;
+
+ /*
+ * Drain input queue on req-socket; we're not interested in spurious
+ * events on this fd so ignore any data.
+ */
+
+ do {
+ l = recv(wpa->req_fd, buf, sizeof(buf), MSG_DONTWAIT);
+ if (l < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+ else
+ return -errno;
+ }
+ } while (l > 0);
+
+ return 0;
+}
+
+static int dispatch_req(struct wfd_wpa_ctrl *wpa, const struct epoll_event *e)
+{
+ int r;
+
+ if (e->events & EPOLLIN) {
+ r = read_req(wpa);
+ if (r < 0)
+ return r;
+ }
+
+ /* handle HUP/ERR last so we drain input first */
+ if (e->events & (EPOLLHUP | EPOLLERR))
+ return -EPIPE;
+
+ return 0;
+}
+
+static int read_tfd(struct wfd_wpa_ctrl *wpa)
+{
+ ssize_t l;
+ uint64_t exp;
+ int r;
+ char buf[10];
+ size_t len = sizeof(buf);
+
+ /* Send PING request if the timer expires. If the wpa_supplicant
+ * doesn't respond in a timely manner, return an error. */
+
+ l = read(wpa->tfd, &exp, sizeof(exp));
+ if (l < 0 && errno != EAGAIN && errno != EINTR) {
+ return -errno;
+ } else if (l == sizeof(exp)) {
+ r = wpa_request(wpa->req_fd, "PING", 4, buf, &len, NULL,
+ &wpa->mask);
+ if (r < 0)
+ return r;
+ if (len != 5 || strncmp(buf, "PONG\n", 5))
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int dispatch_tfd(struct wfd_wpa_ctrl *wpa, const struct epoll_event *e)
+{
+ int r = 0;
+
+ /* Remove tfd from epoll-set on HUP/ERR. This shouldn't happen, but if
+ * it does, just stop receiving events from it. */
+ if (e->events & (EPOLLHUP | EPOLLERR)) {
+ r = -EFAULT;
+ goto error;
+ }
+
+ if (e->events & (EPOLLIN)) {
+ r = read_tfd(wpa);
+ if (r < 0)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ epoll_ctl(wpa->efd, EPOLL_CTL_DEL, wpa->tfd, NULL);
+ return r;
+}
+
+_shl_public_
+int wfd_wpa_ctrl_dispatch(struct wfd_wpa_ctrl *wpa, int timeout)
+{
+ struct epoll_event ev[2], *e;
+ int r, n, i;
+ const size_t max = sizeof(ev) / sizeof(*ev);
+
+ if (!wfd_wpa_ctrl_is_open(wpa))
+ return -ENODEV;
+
+ n = epoll_wait(wpa->efd, ev, max, timeout);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+ else
+ return -errno;
+ } else if (n > max) {
+ n = max;
+ }
+
+ r = 0;
+ for (i = 0; i < n; ++i) {
+ e = &ev[i];
+ if (e->data.ptr == &wpa->ev_fd)
+ r = dispatch_ev(wpa, e);
+ else if (e->data.ptr == &wpa->req_fd)
+ r = dispatch_req(wpa, e);
+ else if (e->data.ptr == &wpa->tfd)
+ r = dispatch_tfd(wpa, e);
+
+ if (r < 0)
+ break;
+ }
+
+ return r;
+}
+
+static int timed_send(int fd, const void *cmd, size_t cmd_len,
+ int64_t *timeout, const sigset_t *mask)
+{
+ bool done = false;
+ int64_t start, t;
+ ssize_t l;
+ int n;
+ struct pollfd fds[1];
+ const size_t max = sizeof(fds) / sizeof(*fds);
+ struct timespec ts;
+
+ start = get_time_us();
+
+ do {
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = fd;
+ fds[0].events = POLLHUP | POLLERR | POLLOUT;
+
+ us_to_timespec(&ts, *timeout);
+ n = ppoll(fds, max, &ts, mask);
+ if (n < 0 && errno != EAGAIN) {
+ return -errno;
+ } else if (!n) {
+ return -ETIMEDOUT;
+ } else if (n > 0) {
+ if (fds[0].revents & (POLLHUP | POLLERR))
+ return -EPIPE;
+
+ l = send(fd, cmd, cmd_len, MSG_NOSIGNAL);
+ if (l < 0 && errno != EAGAIN && errno != EINTR) {
+ return -errno;
+ } else if (l > 0) {
+ /* We don't care how much was sent. If we
+ * couldn't send the whole datagram, we still
+ * try to recv the error reply from
+ * wpa_supplicant. There's no way to split
+ * datagrams so we're screwed in that case. */
+ done = true;
+ }
+ }
+
+ /* recalculate remaining timeout */
+ t = *timeout - (get_time_us() - start);
+ if (t <= 0) {
+ *timeout = 0;
+ if (!done)
+ return -ETIMEDOUT;
+ } else {
+ *timeout = t;
+ }
+ } while (!done);
+
+ return 0;
+}
+
+static int timed_recv(int fd, void *reply, size_t *reply_len, int64_t *timeout,
+ const sigset_t *mask)
+{
+ bool done = false;
+ int64_t start, t;
+ ssize_t l;
+ int n;
+ struct pollfd fds[1];
+ const size_t max = sizeof(fds) / sizeof(*fds);
+ struct timespec ts;
+
+ start = get_time_us();
+
+ do {
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = fd;
+ fds[0].events = POLLHUP | POLLERR | POLLIN;
+
+ us_to_timespec(&ts, *timeout);
+ n = ppoll(fds, max, &ts, mask);
+ if (n < 0 && errno != EAGAIN) {
+ return -errno;
+ } else if (!n) {
+ return -ETIMEDOUT;
+ } else if (n > 0) {
+ if (fds[0].revents & (POLLHUP | POLLERR))
+ return -EPIPE;
+
+ l = recv(fd, reply, *reply_len, MSG_DONTWAIT);
+ if (l < 0 && errno != EAGAIN && errno != EINTR) {
+ return -errno;
+ } else if (l > 0 && *(char*)reply != '<') {
+ /* We ignore any event messages ('<') on this
+ * fd as they're handled via a separate pipe.
+ * Return the received reply unchanged. */
+ *reply_len = l;
+ done = true;
+ }
+ }
+
+ /* recalculate remaining timeout */
+ t = *timeout - (get_time_us() - start);
+ if (t <= 0) {
+ *timeout = 0;
+ if (!done)
+ return -ETIMEDOUT;
+ } else {
+ *timeout = t;
+ }
+ } while (!done);
+
+ return 0;
+}
+
+static int wpa_request(int fd, const void *cmd, size_t cmd_len,
+ void *reply, size_t *reply_len, int64_t *t2,
+ const sigset_t *mask)
+{
+ char buf[REQ_REPLY_MAX];
+ size_t l = REQ_REPLY_MAX;
+ int64_t *t, t1 = -1, max;
+ int r;
+
+ if (!cmd || !cmd_len || !reply || !reply_len)
+ return -EINVAL;
+ if (fd < 0)
+ return -ENODEV;
+
+ if (t2)
+ t = t2;
+ else
+ t = &t1;
+ if (!reply)
+ reply = buf;
+ if (!reply_len)
+ reply_len = &l;
+
+ /* use a maximum of 10s */
+ max = 10LL * 1000LL * 1000LL;
+ if (*t < 0 || *t > max)
+ *t = max;
+
+ /* send() with timeout */
+ r = timed_send(fd, cmd, cmd_len, t, mask);
+ if (r < 0)
+ return r;
+
+ /* recv() with timeout */
+ r = timed_recv(fd, reply, reply_len, t, mask);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int wpa_request_ok(int fd, const void *cmd, size_t cmd_len, int64_t *t,
+ const sigset_t *mask)
+{
+ char buf[REQ_REPLY_MAX];
+ size_t l = REQ_REPLY_MAX;
+ int r;
+
+ r = wpa_request(fd, cmd, cmd_len, buf, &l, t, mask);
+ if (r < 0)
+ return r;
+
+ if (l != 3 || strncmp(buf, "OK\n", 3))
+ return -EINVAL;
+
+ return 0;
+}
+
+_shl_public_
+int wfd_wpa_ctrl_request(struct wfd_wpa_ctrl *wpa, const void *cmd,
+ size_t cmd_len, void *reply, size_t *reply_len,
+ int timeout)
+{
+ int64_t t;
+
+ if (!wpa)
+ return -EINVAL;
+ if (!wfd_wpa_ctrl_is_open(wpa))
+ return -ENODEV;
+
+ /* prevent mult-overflow */
+ if (timeout < 0)
+ timeout = -1;
+ else if (timeout > 1000000)
+ timeout = 1000000;
+ t = timeout * 1000LL;
+
+ return wpa_request(wpa->req_fd, cmd, cmd_len, reply, reply_len, &t,
+ &wpa->mask);
+}
+
+_shl_public_
+int wfd_wpa_ctrl_request_ok(struct wfd_wpa_ctrl *wpa, const void *cmd,
+ size_t cmd_len, int timeout)
+{
+ char buf[REQ_REPLY_MAX];
+ size_t l = REQ_REPLY_MAX;
+ int r;
+
+ if (!wpa)
+ return -EINVAL;
+ r = wfd_wpa_ctrl_request(wpa, cmd, cmd_len, buf, &l, timeout);
+ if (r < 0)
+ return r;
+
+ if (l != 3 || strncmp(buf, "OK\n", 3))
+ return -EINVAL;
+
+ return 0;
+}
diff --git a/src/wpa_parser.c b/src/wpa_parser.c
new file mode 100644
index 0000000..567b851
--- /dev/null
+++ b/src/wpa_parser.c
@@ -0,0 +1,537 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libwfd.h"
+#include "shl_macro.h"
+
+_shl_public_
+void wfd_wpa_event_init(struct wfd_wpa_event *ev)
+{
+ if (!ev)
+ return;
+
+ memset(ev, 0, sizeof(*ev));
+}
+
+_shl_public_
+void wfd_wpa_event_reset(struct wfd_wpa_event *ev)
+{
+ if (!ev)
+ return;
+
+ free(ev->raw);
+
+ switch (ev->type) {
+ case WFD_WPA_EVENT_P2P_DEVICE_FOUND:
+ free(ev->p.p2p_device_found.name);
+ break;
+ case WFD_WPA_EVENT_P2P_GROUP_STARTED:
+ free(ev->p.p2p_group_started.ifname);
+ break;
+ case WFD_WPA_EVENT_P2P_PROV_DISC_SHOW_PIN:
+ free(ev->p.p2p_prov_disc_show_pin.pin);
+ break;
+ default:
+ break;
+ }
+
+ memset(ev, 0, sizeof(*ev));
+}
+
+static const struct event_type {
+ const char *name;
+ size_t len;
+ unsigned int code;
+} event_list[] = {
+
+#define EVENT(_name, _suffix) { \
+ .name = _name, \
+ .len = sizeof(_name) - 1, \
+ .code = WFD_WPA_EVENT_ ## _suffix \
+ }
+
+ /* MUST BE ORDERED ALPHABETICALLY FOR BINARY SEARCH! */
+
+ EVENT("AP-STA-CONNECTED", AP_STA_CONNECTED),
+ EVENT("AP-STA-DISCONNECTED", AP_STA_DISCONNECTED),
+ EVENT("P2P-DEVICE-FOUND", P2P_DEVICE_FOUND),
+ EVENT("P2P-FIND-STOPPED", P2P_FIND_STOPPED),
+ EVENT("P2P-GO-NEG-FAILURE", P2P_GO_NEG_FAILURE),
+ EVENT("P2P-GO-NEG-REQUEST", P2P_GO_NEG_REQUEST),
+ EVENT("P2P-GO-NEG-SUCCESS", P2P_GO_NEG_SUCCESS),
+ EVENT("P2P-GROUP-FORMATION-FAILURE", P2P_GROUP_FORMATION_FAILURE),
+ EVENT("P2P-GROUP-FORMATION-SUCCESS", P2P_GROUP_FORMATION_SUCCESS),
+ EVENT("P2P-GROUP-REMOVED", P2P_GROUP_REMOVED),
+ EVENT("P2P-GROUP-STARTED", P2P_GROUP_STARTED),
+ EVENT("P2P-INVITATION-RECEIVED", P2P_INVITATION_RECEIVED),
+ EVENT("P2P-INVITATION-RESULT", P2P_INVITATION_RESULT),
+ EVENT("P2P-PROV-DISC-ENTER-PIN", P2P_PROV_DISC_ENTER_PIN),
+ EVENT("P2P-PROV-DISC-PBC-REQ", P2P_PROV_DISC_PBC_REQ),
+ EVENT("P2P-PROV-DISC-PBC-RESP", P2P_PROV_DISC_PBC_RESP),
+ EVENT("P2P-PROV-DISC-SHOW-PIN", P2P_PROV_DISC_SHOW_PIN),
+ EVENT("P2P-SERV-DISC-REQ", P2P_SERV_DISC_REQ),
+ EVENT("P2P-SERV-DISC-RESP", P2P_SERV_DISC_RESP),
+
+#undef EVENT
+};
+
+_shl_public_
+const char *wfd_wpa_event_name(unsigned int type)
+{
+ size_t i, max;
+
+ max = sizeof(event_list) / sizeof(*event_list);
+
+ for (i = 0; i < max; ++i) {
+ if (event_list[i].code == type)
+ return event_list[i].name;
+ }
+
+ return "UNKNOWN";
+}
+
+static int event_comp(const void *key, const void *type)
+{
+ const struct event_type *t;
+ const char *k;
+ int r;
+
+ k = key;
+ t = type;
+
+ r = strncmp(k, t->name, t->len);
+ if (r)
+ return r;
+
+ if (k[t->len] != 0 && k[t->len] != ' ')
+ return 1;
+
+ return 0;
+}
+
+static char *tokenize(const char *src, size_t *num)
+{
+ char *buf, *dst;
+ char last_c;
+ size_t n;
+ bool quoted, escaped;
+
+ buf = malloc(strlen(src) + 1);
+ if (!buf)
+ return NULL;
+
+ dst = buf;
+ last_c = 0;
+ n = 0;
+ *dst = 0;
+ quoted = 0;
+ escaped = 0;
+
+ for ( ; *src; ++src) {
+ if (quoted) {
+ if (escaped) {
+ escaped = 0;
+ last_c = *src;
+ *dst++ = last_c;
+ } else if (*src == '\'') {
+ quoted = 0;
+ } else if (*src == '\\') {
+ escaped = 1;
+ } else {
+ last_c = *src;
+ *dst++ = last_c;
+ }
+ } else {
+ if (*src == ' ' ||
+ *src == '\n' ||
+ *src == '\t' ||
+ *src == '\r') {
+ if (last_c) {
+ *dst++ = 0;
+ ++n;
+ }
+ last_c = 0;
+ } else if (*src == '\'') {
+ quoted = 1;
+ escaped = 0;
+ last_c = *src;
+ } else {
+ last_c = *src;
+ *dst++ = last_c;
+ }
+ }
+ }
+
+ if (last_c) {
+ *dst = 0;
+ ++n;
+ }
+
+ *num = n;
+ return buf;
+}
+
+static int parse_mac(char *buf, const char *src)
+{
+ int r, a1, a2, a3, a4, a5, a6;
+
+ if (strlen(src) > 17)
+ return -EINVAL;
+
+ r = sscanf(src, "%2x:%2x:%2x:%2x:%2x:%2x",
+ &a1, &a2, &a3, &a4, &a5, &a6);
+ if (r != 6)
+ return -EINVAL;
+
+ strcpy(buf, src);
+ return 0;
+}
+
+static int parse_ap_sta_connected(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+
+ if (num < 1)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.ap_sta_connected.mac, tokens);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int parse_ap_sta_disconnected(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+
+ if (num < 1)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.ap_sta_disconnected.mac, tokens);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int parse_p2p_device_found(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+ size_t i;
+
+ if (num < 2)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.p2p_device_found.peer_mac, tokens);
+ if (r < 0)
+ return r;
+
+ tokens += strlen(tokens) + 1;
+ for (i = 1; i < num; ++i, tokens += strlen(tokens) + 1) {
+ if (strncmp(tokens, "name=", 5))
+ continue;
+
+ ev->p.p2p_device_found.name = strdup(&tokens[5]);
+ if (!ev->p.p2p_device_found.name)
+ return -ENOMEM;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int parse_p2p_go_neg_success(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+ size_t i;
+ bool has_role = false, has_peer = false;
+
+ if (num < 2)
+ return -EINVAL;
+
+ for (i = 0; i < num; ++i, tokens += strlen(tokens) + 1) {
+ if (!strncmp(tokens, "role=", 5)) {
+ if (!strcmp(&tokens[5], "GO"))
+ ev->p.p2p_go_neg_success.role = WFD_WPA_EVENT_ROLE_GO;
+ else if (!strcmp(&tokens[5], "client"))
+ ev->p.p2p_go_neg_success.role = WFD_WPA_EVENT_ROLE_CLIENT;
+ else
+ return -EINVAL;
+
+ has_role = true;
+ } else if (!strncmp(tokens, "peer_dev=", 9)) {
+ r = parse_mac(ev->p.p2p_go_neg_success.peer_mac,
+ &tokens[9]);
+ if (r < 0)
+ return r;
+
+ has_peer = true;
+ }
+ }
+
+ return (has_role && has_peer) ? 0 : -EINVAL;
+}
+
+static int parse_p2p_group_started(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+ size_t i;
+
+ if (num < 3)
+ return -EINVAL;
+
+ ev->p.p2p_group_started.ifname = strdup(tokens);
+ if (!ev->p.p2p_group_started.ifname)
+ return -ENOMEM;
+
+ tokens += strlen(tokens) + 1;
+
+ if (!strcmp(tokens, "GO"))
+ ev->p.p2p_group_started.role = WFD_WPA_EVENT_ROLE_GO;
+ else if (!strcmp(tokens, "client"))
+ ev->p.p2p_group_started.role = WFD_WPA_EVENT_ROLE_CLIENT;
+ else
+ return -EINVAL;
+
+ tokens += strlen(tokens) + 1;
+
+ for (i = 2; i < num; ++i, tokens += strlen(tokens) + 1) {
+ if (strncmp(tokens, "go_dev_addr=", 12))
+ continue;
+
+ r = parse_mac(ev->p.p2p_group_started.go_mac, &tokens[12]);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int parse_p2p_group_removed(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ if (num < 2)
+ return -EINVAL;
+
+ ev->p.p2p_group_removed.ifname = strdup(tokens);
+ if (!ev->p.p2p_group_removed.ifname)
+ return -ENOMEM;
+
+ tokens += strlen(tokens) + 1;
+
+ if (!strcmp(tokens, "GO"))
+ ev->p.p2p_group_removed.role = WFD_WPA_EVENT_ROLE_GO;
+ else if (!strcmp(tokens, "client"))
+ ev->p.p2p_group_removed.role = WFD_WPA_EVENT_ROLE_CLIENT;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int parse_p2p_prov_disc_show_pin(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+
+ if (num < 2)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.p2p_prov_disc_show_pin.peer_mac, tokens);
+ if (r < 0)
+ return r;
+
+ tokens += strlen(tokens) + 1;
+ ev->p.p2p_prov_disc_show_pin.pin = strdup(tokens);
+ if (!ev->p.p2p_prov_disc_show_pin.pin)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int parse_p2p_prov_disc_enter_pin(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+
+ if (num < 1)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.p2p_prov_disc_enter_pin.peer_mac, tokens);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int parse_p2p_prov_disc_pbc_req(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+
+ if (num < 1)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.p2p_prov_disc_pbc_req.peer_mac, tokens);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int parse_p2p_prov_disc_pbc_resp(struct wfd_wpa_event *ev,
+ char *tokens, size_t num)
+{
+ int r;
+
+ if (num < 1)
+ return -EINVAL;
+
+ r = parse_mac(ev->p.p2p_prov_disc_pbc_resp.peer_mac, tokens);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+_shl_public_
+int wfd_wpa_event_parse(struct wfd_wpa_event *ev, const char *event)
+{
+ const char *t;
+ char *end, *tokens = NULL;
+ size_t num;
+ struct event_type *code;
+ int r;
+
+ if (!ev || !event)
+ return -EINVAL;
+
+ wfd_wpa_event_reset(ev);
+
+ if (*event == '<') {
+ t = strchr(event, '>');
+ if (!t)
+ goto unknown;
+
+ ++t;
+ ev->priority = strtoul(event + 1, &end, 10);
+ if (ev->priority >= WFD_WPA_EVENT_P_COUNT ||
+ end + 1 != t ||
+ event[1] == '+' ||
+ event[1] == '-')
+ ev->priority = WFD_WPA_EVENT_P_MSGDUMP;
+ } else {
+ t = event;
+ ev->priority = WFD_WPA_EVENT_P_MSGDUMP;
+ }
+
+ code = bsearch(t, event_list,
+ sizeof(event_list) / sizeof(*event_list),
+ sizeof(*event_list),
+ event_comp);
+ if (!code)
+ goto unknown;
+
+ ev->type = code->code;
+ t += code->len;
+ while (*t == ' ')
+ ++t;
+
+ ev->raw = strdup(t);
+ if (!ev->raw) {
+ r = -ENOMEM;
+ goto error;
+ }
+
+ tokens = tokenize(ev->raw, &num);
+ if (!tokens) {
+ r = -ENOMEM;
+ goto error;
+ }
+
+ switch (ev->type) {
+ case WFD_WPA_EVENT_AP_STA_CONNECTED:
+ r = parse_ap_sta_connected(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_AP_STA_DISCONNECTED:
+ r = parse_ap_sta_disconnected(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_DEVICE_FOUND:
+ r = parse_p2p_device_found(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_GO_NEG_SUCCESS:
+ r = parse_p2p_go_neg_success(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_GROUP_STARTED:
+ r = parse_p2p_group_started(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_GROUP_REMOVED:
+ r = parse_p2p_group_removed(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_PROV_DISC_SHOW_PIN:
+ r = parse_p2p_prov_disc_show_pin(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_PROV_DISC_ENTER_PIN:
+ r = parse_p2p_prov_disc_enter_pin(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_PROV_DISC_PBC_REQ:
+ r = parse_p2p_prov_disc_pbc_req(ev, tokens, num);
+ break;
+ case WFD_WPA_EVENT_P2P_PROV_DISC_PBC_RESP:
+ r = parse_p2p_prov_disc_pbc_resp(ev, tokens, num);
+ break;
+ default:
+ r = 0;
+ break;
+ }
+
+ free(tokens);
+
+ if (r < 0)
+ goto error;
+
+ return 0;
+
+unknown:
+ ev->type = WFD_WPA_EVENT_UNKNOWN;
+ return 0;
+
+error:
+ wfd_wpa_event_reset(ev);
+ return r;
+}
diff --git a/test/test_wpa.c b/test/test_wpa.c
new file mode 100644
index 0000000..afc98dc
--- /dev/null
+++ b/test/test_wpa.c
@@ -0,0 +1,168 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test_common.h"
+
+static void parse(struct wfd_wpa_event *ev, const char *event)
+{
+ int r;
+
+ wfd_wpa_event_init(ev);
+ r = wfd_wpa_event_parse(ev, event);
+ ck_assert_msg(!r, "cannot parse event %s", event);
+ ck_assert(ev->priority < WFD_WPA_EVENT_P_COUNT);
+}
+
+static const char *event_list[] = {
+ [WFD_WPA_EVENT_UNKNOWN] = "",
+ [WFD_WPA_EVENT_AP_STA_CONNECTED] = "AP-STA-CONNECTED 00:00:00:00:00:00",
+ [WFD_WPA_EVENT_AP_STA_DISCONNECTED] = "AP-STA-DISCONNECTED 00:00:00:00:00:00",
+ [WFD_WPA_EVENT_P2P_DEVICE_FOUND] = "P2P-DEVICE-FOUND 00:00:00:00:00:00 name=some-name",
+ [WFD_WPA_EVENT_P2P_FIND_STOPPED] = "P2P-FIND-STOPPED",
+ [WFD_WPA_EVENT_P2P_GO_NEG_REQUEST] = "P2P-GO-NEG-REQUEST",
+ [WFD_WPA_EVENT_P2P_GO_NEG_SUCCESS] = "P2P-GO-NEG-SUCCESS role=GO peer_dev=00:00:00:00:00:00",
+ [WFD_WPA_EVENT_P2P_GO_NEG_FAILURE] = "P2P-GO-NEG-FAILURE",
+ [WFD_WPA_EVENT_P2P_GROUP_FORMATION_SUCCESS] = "P2P-GROUP-FORMATION-SUCCESS",
+ [WFD_WPA_EVENT_P2P_GROUP_FORMATION_FAILURE] = "P2P-GROUP-FORMATION-FAILURE",
+ [WFD_WPA_EVENT_P2P_GROUP_STARTED] = "P2P-GROUP-STARTED p2p-wlan0-0 client go_dev_addr=00:00:00:00:00:00",
+ [WFD_WPA_EVENT_P2P_GROUP_REMOVED] = "P2P-GROUP-REMOVED p2p-wlan0-0 GO",
+ [WFD_WPA_EVENT_P2P_PROV_DISC_SHOW_PIN] = "P2P-PROV-DISC-SHOW-PIN 00:00:00:00:00:00 pin",
+ [WFD_WPA_EVENT_P2P_PROV_DISC_ENTER_PIN] = "P2P-PROV-DISC-ENTER-PIN 00:00:00:00:00:00",
+ [WFD_WPA_EVENT_P2P_PROV_DISC_PBC_REQ] = "P2P-PROV-DISC-PBC-REQ 00:00:00:00:00:00",
+ [WFD_WPA_EVENT_P2P_PROV_DISC_PBC_RESP] = "P2P-PROV-DISC-PBC-RESP 00:00:00:00:00:00",
+ [WFD_WPA_EVENT_P2P_SERV_DISC_REQ] = "P2P-SERV-DISC-REQ",
+ [WFD_WPA_EVENT_P2P_SERV_DISC_RESP] = "P2P-SERV-DISC-RESP",
+ [WFD_WPA_EVENT_P2P_INVITATION_RECEIVED] = "P2P-INVITATION-RECEIVED",
+ [WFD_WPA_EVENT_P2P_INVITATION_RESULT] = "P2P-INVITATION-RESULT",
+ [WFD_WPA_EVENT_COUNT] = NULL
+};
+
+START_TEST(test_wpa_parser)
+{
+ struct wfd_wpa_event ev;
+ int i;
+
+ parse(&ev, "");
+ ck_assert(ev.type == WFD_WPA_EVENT_UNKNOWN);
+
+ parse(&ev, "asdf");
+ ck_assert(ev.type == WFD_WPA_EVENT_UNKNOWN);
+
+ for (i = 0; i < WFD_WPA_EVENT_COUNT; ++i) {
+ ck_assert_msg(event_list[i] != NULL, "event %d missing", i);
+ parse(&ev, event_list[i]);
+ ck_assert_msg(ev.type == i, "event %d invalid", i);
+ }
+
+ parse(&ev, "<5>AP-STA-CONNECTED 0:0:0:0:0:0");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_MSGDUMP);
+ ck_assert(ev.type == WFD_WPA_EVENT_AP_STA_CONNECTED);
+
+ parse(&ev, "<4>AP-STA-CONNECTED 0:0:0:0:0:0");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_AP_STA_CONNECTED);
+
+ parse(&ev, "<4>AP-STA-CONNECTED2");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_UNKNOWN);
+
+ parse(&ev, "<4asdf>AP-STA-CONNECTED 0:0:0:0:0:0");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_MSGDUMP);
+ ck_assert(ev.type == WFD_WPA_EVENT_AP_STA_CONNECTED);
+
+ parse(&ev, "<4>AP-STA-CONNECTED 0:0:0:0:0:0");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_AP_STA_CONNECTED);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.raw, "0:0:0:0:0:0"));
+
+ parse(&ev, "<4>AP-STA something else");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_UNKNOWN);
+ ck_assert(!ev.raw);
+}
+END_TEST
+
+START_TEST(test_wpa_parser_payload)
+{
+ struct wfd_wpa_event ev;
+
+ parse(&ev, "<4>P2P-DEVICE-FOUND 0:0:0:0:0:0 name=some-name");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_P2P_DEVICE_FOUND);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.raw, "0:0:0:0:0:0 name=some-name"));
+ ck_assert(!strcmp(ev.p.p2p_device_found.peer_mac, "0:0:0:0:0:0"));
+ ck_assert(!strcmp(ev.p.p2p_device_found.name, "some-name"));
+
+ parse(&ev, "<4>P2P-DEVICE-FOUND 0:0:0:0:0:0 name=some-'name\\\\\\''");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_P2P_DEVICE_FOUND);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.p.p2p_device_found.peer_mac, "0:0:0:0:0:0"));
+ ck_assert(!strcmp(ev.p.p2p_device_found.name, "some-name\\'"));
+
+ parse(&ev, "<4>P2P-PROV-DISC-SHOW-PIN 0:0:0:0:0:0 1234567890");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_P2P_PROV_DISC_SHOW_PIN);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.p.p2p_prov_disc_show_pin.peer_mac, "0:0:0:0:0:0"));
+ ck_assert(!strcmp(ev.p.p2p_prov_disc_show_pin.pin, "1234567890"));
+
+ parse(&ev, "<4>P2P-GO-NEG-SUCCESS role=GO peer_dev=0:0:0:0:0:0");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_P2P_GO_NEG_SUCCESS);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.p.p2p_go_neg_success.peer_mac, "0:0:0:0:0:0"));
+ ck_assert(ev.p.p2p_go_neg_success.role == WFD_WPA_EVENT_ROLE_GO);
+
+ parse(&ev, "<4>P2P-GROUP-STARTED p2p-wlan0-0 client go_dev_addr=0:0:0:0:0:0");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_P2P_GROUP_STARTED);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.p.p2p_group_started.go_mac, "0:0:0:0:0:0"));
+ ck_assert(!strcmp(ev.p.p2p_group_started.ifname, "p2p-wlan0-0"));
+ ck_assert(ev.p.p2p_group_started.role == WFD_WPA_EVENT_ROLE_CLIENT);
+
+ parse(&ev, "<4>P2P-GROUP-REMOVED p2p-wlan0-1 GO");
+ ck_assert(ev.priority == WFD_WPA_EVENT_P_ERROR);
+ ck_assert(ev.type == WFD_WPA_EVENT_P2P_GROUP_REMOVED);
+ ck_assert(ev.raw != NULL);
+ ck_assert(!strcmp(ev.p.p2p_group_removed.ifname, "p2p-wlan0-1"));
+ ck_assert(ev.p.p2p_group_removed.role == WFD_WPA_EVENT_ROLE_GO);
+}
+END_TEST
+
+TEST_DEFINE_CASE(parser)
+ TEST(test_wpa_parser)
+ TEST(test_wpa_parser_payload)
+TEST_END_CASE
+
+TEST_DEFINE(
+ TEST_SUITE(wpa,
+ TEST_CASE(parser),
+ TEST_END
+ )
+)