WPS ER: Subscribe to UPnP events
[libeap.git] / src / wps / wps_er.c
index a9f932a..bfa27e2 100644 (file)
@@ -17,7 +17,9 @@
 #include "common.h"
 #include "uuid.h"
 #include "eloop.h"
+#include "httpread.h"
 #include "http_client.h"
+#include "http_server.h"
 #include "upnp_xml.h"
 #include "wps_i.h"
 #include "wps_upnp.h"
@@ -25,7 +27,6 @@
 
 
 /* TODO:
- * start own HTTP server for receiving events
  * send notification of new AP device with wpa_msg
  * re-send notifications with wpa_msg if ER re-started (to update wpa_gui-qt4)
  * (also re-send SSDP M-SEARCH in this case to find new APs)
@@ -37,6 +38,7 @@ static void wps_er_ap_timeout(void *eloop_data, void *user_ctx);
 
 struct wps_er_ap {
        struct wps_er_ap *next;
+       struct wps_er *er;
        struct in_addr addr;
        char *location;
        struct http_client *http;
@@ -55,6 +57,9 @@ struct wps_er_ap {
        char *scpd_url;
        char *control_url;
        char *event_sub_url;
+
+       int subscribed;
+       unsigned int id;
 };
 
 struct wps_er {
@@ -67,6 +72,9 @@ struct wps_er {
        int multicast_sd;
        int ssdp_sd;
        struct wps_er_ap *ap;
+       struct http_server *http_srv;
+       int http_port;
+       unsigned int next_ap_id;
 };
 
 
@@ -89,8 +97,21 @@ static struct wps_er_ap * wps_er_ap_get(struct wps_er *er,
 }
 
 
+static struct wps_er_ap * wps_er_ap_get_id(struct wps_er *er, unsigned int id)
+{
+       struct wps_er_ap *ap;
+       for (ap = er->ap; ap; ap = ap->next) {
+               if (ap->id == id)
+                       break;
+       }
+       return ap;
+}
+
+
 static void wps_er_ap_free(struct wps_er *er, struct wps_er_ap *ap)
 {
+       /* TODO: if ap->subscribed, unsubscribe from events if the AP is still
+        * alive */
        wpa_printf(MSG_DEBUG, "WPS ER: Removing AP entry for %s (%s)",
                   inet_ntoa(ap->addr), ap->location);
        eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
@@ -125,6 +146,74 @@ static void wps_er_ap_timeout(void *eloop_data, void *user_ctx)
 }
 
 
+static void wps_er_http_subscribe_cb(void *ctx, struct http_client *c,
+                                    enum http_client_event event)
+{
+       struct wps_er_ap *ap = ctx;
+
+       switch (event) {
+       case HTTP_CLIENT_OK:
+               wpa_printf(MSG_DEBUG, "WPS ER: Subscribed to events");
+               break;
+       case HTTP_CLIENT_FAILED:
+       case HTTP_CLIENT_INVALID_REPLY:
+       case HTTP_CLIENT_TIMEOUT:
+               wpa_printf(MSG_DEBUG, "WPS ER: Failed to subscribe to events");
+               break;
+       }
+       http_client_free(ap->http);
+       ap->http = NULL;
+}
+
+
+static void wps_er_subscribe(struct wps_er_ap *ap)
+{
+       struct wpabuf *req;
+       struct sockaddr_in dst;
+       char *url, *path;
+
+       if (ap->event_sub_url == NULL) {
+               wpa_printf(MSG_DEBUG, "WPS ER: No eventSubURL - cannot "
+                          "subscribe");
+               return;
+       }
+       if (ap->http) {
+               wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP request - cannot "
+                          "send subscribe request");
+               return;
+       }
+
+       url = http_client_url_parse(ap->event_sub_url, &dst, &path);
+       if (url == NULL) {
+               wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse eventSubURL");
+               return;
+       }
+
+       req = wpabuf_alloc(os_strlen(ap->event_sub_url) + 1000);
+       if (req == NULL) {
+               os_free(url);
+               return;
+       }
+       wpabuf_printf(req,
+                     "SUBSCRIBE %s HTTP/1.1\r\n"
+                     "HOST: %s:%d\r\n"
+                     "CALLBACK: <http://%s:%d/event/%d>\r\n"
+                     "NT: upnp:event\r\n"
+                     "TIMEOUT: Second-%d\r\n"
+                     "\r\n",
+                     path, inet_ntoa(dst.sin_addr), ntohs(dst.sin_port),
+                     ap->er->ip_addr_text, ap->er->http_port, ap->id, 1800);
+       os_free(url);
+       wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Subscription request",
+                         wpabuf_head(req), wpabuf_len(req));
+
+       ap->http = http_client_addr(&dst, req, 1000, wps_er_http_subscribe_cb,
+                                   ap);
+       if (ap->http == NULL)
+               wpabuf_free(req);
+}
+
+
 static void wps_er_parse_device_description(struct wps_er_ap *ap,
                                            struct wpabuf *reply)
 {
@@ -177,8 +266,6 @@ static void wps_er_parse_device_description(struct wps_er_ap *ap,
        ap->event_sub_url = http_link_update(
                xml_get_first_item(data, "eventSubURL"), ap->location);
        wpa_printf(MSG_DEBUG, "WPS ER: eventSubURL='%s'", ap->event_sub_url);
-
-       /* TODO: subscribe for events */
 }
 
 
@@ -187,6 +274,7 @@ static void wps_er_http_dev_desc_cb(void *ctx, struct http_client *c,
 {
        struct wps_er_ap *ap = ctx;
        struct wpabuf *reply;
+       int subscribe = 0;
 
        switch (event) {
        case HTTP_CLIENT_OK:
@@ -194,6 +282,7 @@ static void wps_er_http_dev_desc_cb(void *ctx, struct http_client *c,
                if (reply == NULL)
                        break;
                wps_er_parse_device_description(ap, reply);
+               subscribe = 1;
                break;
        case HTTP_CLIENT_FAILED:
        case HTTP_CLIENT_INVALID_REPLY:
@@ -203,6 +292,8 @@ static void wps_er_http_dev_desc_cb(void *ctx, struct http_client *c,
        }
        http_client_free(ap->http);
        ap->http = NULL;
+       if (subscribe)
+               wps_er_subscribe(ap);
 }
 
 
@@ -222,6 +313,8 @@ static void wps_er_ap_add(struct wps_er *er, struct in_addr *addr,
        ap = os_zalloc(sizeof(*ap));
        if (ap == NULL)
                return;
+       ap->er = er;
+       ap->id = ++er->next_ap_id;
        ap->location = os_strdup(location);
        if (ap->location == NULL) {
                os_free(ap);
@@ -396,11 +489,65 @@ static void wps_er_send_ssdp_msearch(struct wps_er *er)
 }
 
 
+static void wps_er_http_event(struct wps_er *er, struct http_request *req,
+                             unsigned int ap_id)
+{
+       struct wps_er_ap *ap = wps_er_ap_get_id(er, ap_id);
+       if (ap == NULL) {
+               wpa_printf(MSG_DEBUG, "WPS ER: HTTP event from unknown AP id "
+                          "%u", ap_id);
+               return;
+       }
+       wpa_printf(MSG_MSGDUMP, "WPS ER: HTTP event from AP id %u: %s",
+                  ap_id, http_request_get_data(req));
+       /* TODO */
+       http_request_deinit(req);
+}
+
+
+static void wps_er_http_notify(struct wps_er *er, struct http_request *req)
+{
+       char *uri = http_request_get_uri(req);
+
+       if (os_strncmp(uri, "/event/", 7) == 0) {
+               wps_er_http_event(er, req, atoi(uri + 7));
+       } else {
+               wpa_printf(MSG_DEBUG, "WPS ER: Unknown HTTP NOTIFY for '%s'",
+                          uri);
+               http_request_deinit(req);
+       }
+}
+
+
+static void wps_er_http_req(void *ctx, struct http_request *req)
+{
+       struct wps_er *er = ctx;
+       struct sockaddr_in *cli = http_request_get_cli_addr(req);
+       enum httpread_hdr_type type = http_request_get_type(req);
+       wpa_printf(MSG_DEBUG, "WPS ER: HTTP request: '%s' (type %d) from "
+                  "%s:%d",
+                  http_request_get_uri(req), type,
+                  inet_ntoa(cli->sin_addr), ntohs(cli->sin_port));
+
+       switch (type) {
+       case HTTPREAD_HDR_TYPE_NOTIFY:
+               wps_er_http_notify(er, req);
+               break;
+       default:
+               wpa_printf(MSG_DEBUG, "WPS ER: Unsupported HTTP request type "
+                          "%d", type);
+               http_request_deinit(req);
+               break;
+       }
+}
+
+
 struct wps_er *
 wps_er_init(struct wps_context *wps, const char *ifname)
 {
        struct wps_er *er;
        struct wps_registrar_config rcfg;
+       struct in_addr addr;
 
        er = os_zalloc(sizeof(*er));
        if (er == NULL)
@@ -453,6 +600,14 @@ wps_er_init(struct wps_context *wps, const char *ifname)
                return NULL;
        }
 
+       addr.s_addr = er->ip_addr;
+       er->http_srv = http_server_init(&addr, -1, wps_er_http_req, er);
+       if (er->http_srv == NULL) {
+               wps_er_deinit(er);
+               return NULL;
+       }
+       er->http_port = http_server_get_port(er->http_srv);
+
        wpa_printf(MSG_DEBUG, "WPS ER: Start (ifname=%s ip_addr=%s "
                   "mac_addr=%s)",
                   er->ifname, er->ip_addr_text, er->mac_addr_text);
@@ -467,6 +622,7 @@ void wps_er_deinit(struct wps_er *er)
 {
        if (er == NULL)
                return;
+       http_server_deinit(er->http_srv);
        wps_er_ap_remove_all(er);
        if (er->multicast_sd >= 0) {
                eloop_unregister_sock(er->multicast_sd, EVENT_TYPE_READ);