diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-02-05 15:36:51 +0100 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2014-02-05 15:36:51 +0100 |
commit | 3a17743bd82dad3400c44b7811c0fadac4c2828c (patch) | |
tree | 1636cca15cf2d4a40114a17a330f26372ce57eb3 | |
parent | 6d57a6a0d910d78c1279a9463ca2bae7c6a6cdfb (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-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 12 | ||||
-rw-r--r-- | docs/libwfd.sym | 19 | ||||
-rw-r--r-- | src/libwfd.h | 144 | ||||
-rw-r--r-- | src/wpa_ctrl.c | 794 | ||||
-rw-r--r-- | src/wpa_parser.c | 537 | ||||
-rw-r--r-- | test/test_wpa.c | 168 |
7 files changed, 1673 insertions, 2 deletions
@@ -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 + ) +) |