Update copyright dates
[src/app-framework-binder.git] / src / afb-ws-client.c
index 6ed3f50..d1cea8c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 IoT.bzh
+ * Copyright (C) 2015-2020 "IoT.bzh"
  * Author: José Bollo <jose.bollo@iot.bzh>
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netdb.h>
+#include <fcntl.h>
 
 #include "afb-wsj1.h"
+#include "fdev-systemd.h"
 
 /**************** WebSocket handshake ****************************/
 
@@ -50,14 +52,6 @@ static const char *compkeys[32] = {
        "MHiEc+Qc8w/SJ3zMHEM8pA==", "FVCxLBmoil3gY0jSX3aNJ6kR/t4="
 };
 
-static const char websocket_s[] = "websocket";
-static const char sec_websocket_key_s[] = "Sec-WebSocket-Key";
-static const char sec_websocket_version_s[] = "Sec-WebSocket-Version";
-static const char sec_websocket_accept_s[] = "Sec-WebSocket-Accept";
-static const char sec_websocket_protocol_s[] = "Sec-WebSocket-Protocol";
-
-static const char vseparators[] = " \t,";
-
 /* get randomly a pair of key/accept value */
 static void getkeypair(const char **key, const char **ack)
 {
@@ -82,8 +76,12 @@ static char *strjoin(int count, const char **strings, const char *separ)
                for(count = 0 ; strings[count] != NULL ; count++);
 
        /* compute the length of the result */
-       length = 0;
-       if (count != 0) {
+       if (count == 0)
+               length = 0;
+       else {
+               length = (unsigned)(count - 1) * strlen(separ);
+               for (idx = 0 ; idx < count ; idx ++)
+                       length += strlen(strings[idx]);
        }
 
        /* allocates the result */
@@ -104,10 +102,11 @@ static char *strjoin(int count, const char **strings, const char *separ)
 }
 
 /* creates the http message for the request */
-static int make_request(char **request, const char *path, const char *key, const char *protocols)
+static int make_request(char **request, const char *path, const char *host, const char *key, const char *protocols)
 {
-       int rc = asprintf(request, 
-                       "GET %s HTTP1.1\r\n"
+       int rc = asprintf(request,
+                       "GET %s HTTP/1.1\r\n"
+                       "Host: %s\r\n"
                        "Upgrade: websocket\r\n"
                        "Connection: Upgrade\r\n"
                        "Sec-WebSocket-Version: 13\r\n"
@@ -116,6 +115,7 @@ static int make_request(char **request, const char *path, const char *key, const
                        "Content-Length: 0\r\n"
                        "\r\n"
                        , path
+                       , host
                        , key
                        , protocols
                );
@@ -128,7 +128,7 @@ static int make_request(char **request, const char *path, const char *key, const
 }
 
 /* create the request and send it to fd, returns the expected accept string */
-static const char *send_request(int fd, const char **protocols, const char *path)
+static const char *send_request(int fd, const char **protocols, const char *path, const char *host)
 {
        const char *key, *ack;
        char *protolist, *request;
@@ -141,7 +141,7 @@ static const char *send_request(int fd, const char **protocols, const char *path
 
        /* create the request */
        getkeypair(&key, &ack);
-       length = make_request(&request, path, key, protolist);
+       length = make_request(&request, path, host, key, protolist);
        free(protolist);
        if (length < 0)
                return NULL;
@@ -194,15 +194,15 @@ static int receive_response(int fd, const char **protocols, const char *ack)
                goto error;
        len = strcspn(line, " ");
        if (len != 8 || 0 != strncmp(line, "HTTP/1.1", 8))
-               goto error;
+               goto abort;
        it = line + len;
-       len = strspn(line, " ");
+       len = strspn(it, " ");
        if (len == 0)
-               goto error;
+               goto abort;
        it += len;
-       len = strcspn(line, " ");
-       if (len != 3 || 0 != strncmp(line, "101", 3))
-               goto error;
+       len = strcspn(it, " ");
+       if (len != 3 || 0 != strncmp(it, "101", 3))
+               goto abort;
 
        /* reads the rest of the response until empty line */
        clen = 0;
@@ -243,20 +243,18 @@ static int receive_response(int fd, const char **protocols, const char *ack)
        if (clen > 0) {
                while (read(fd, line, len) < 0 && errno == EINTR);
        }
-       if (haserr != 0)
-               result = -1;
-       else if (result < 0) {
-               result = 0;
-               while(protocols[result] != NULL)
-                       result++;
-       }
-error:
+       if (haserr != 0 || result < 0)
+               goto abort;
        return result;
+abort:
+       errno = ECONNABORTED;
+error:
+       return -1;
 }
 
-static int negociate(int fd, const char **protocols, const char *path)
+static int negociate(int fd, const char **protocols, const char *path, const char *host)
 {
-       const char *ack = send_request(fd, protocols, path);
+       const char *ack = send_request(fd, protocols, path, host);
        return ack == NULL ? -1 : receive_response(fd, protocols, ack);
 }
 
@@ -296,8 +294,7 @@ static int parse_uri(const char *uri, char **host, char **service, const char **
        /* make the result */
        *host = strndup(h, hlen);
        if (*host != NULL) {
-               return -1;
-               *service = plen ? strndup(h, hlen) : strdup("http");
+               *service = plen ? strndup(p, plen) : strdup("http");
                if (*service != NULL) {
                        *path = uri;
                        return 0;
@@ -310,7 +307,6 @@ invalid:
        errno = EINVAL;
 error:
        return -1;
-       
 }
 
 
@@ -318,13 +314,14 @@ error:
 
 static const char *proto_json1[2] = { "x-afb-ws-json1",        NULL };
 
-struct afb_wsj1 *afb_ws_client_connect_wsj1(const char *uri, struct afb_wsj1_itf *itf, void *closure)
+struct afb_wsj1 *afb_ws_client_connect_wsj1(struct sd_event *eloop, const char *uri, struct afb_wsj1_itf *itf, void *closure)
 {
        int rc, fd;
-       char *host, *service;
+       char *host, *service, xhost[32];
        const char *path;
        struct addrinfo hint, *rai, *iai;
        struct afb_wsj1 *result;
+       struct fdev *fdev;
 
        /* scan the uri */
        rc = parse_uri(uri, &host, &service, &path);
@@ -347,15 +344,26 @@ struct afb_wsj1 *afb_ws_client_connect_wsj1(const char *uri, struct afb_wsj1_itf
        result = NULL;
        iai = rai;
        while (iai != NULL) {
+               struct sockaddr_in *a = (struct sockaddr_in*)(iai->ai_addr);
+               unsigned char *ipv4 = (unsigned char*)&(a->sin_addr.s_addr);
+               unsigned char *port = (unsigned char*)&(a->sin_port);
+               sprintf(xhost, "%d.%d.%d.%d:%d",
+                       (int)ipv4[0], (int)ipv4[1], (int)ipv4[2], (int)ipv4[3],
+                       (((int)port[0]) << 8)|(int)port[1]);
                fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
                if (fd >= 0) {
                        rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
                        if (rc == 0) {
-                               rc = negociate(fd, proto_json1, path);
+                               rc = negociate(fd, proto_json1, path, xhost);
                                if (rc == 0) {
-                                       result = afb_wsj1_create(fd, itf, closure);
-                                       if (result != NULL)
-                                               break;
+                                       fdev = fdev_systemd_create(eloop, fd);
+                                       if (fdev) {
+                                               result = afb_wsj1_create(fdev, itf, closure);
+                                               if (result != NULL) {
+                                                       fcntl(fd, F_SETFL, O_NONBLOCK);
+                                                       break;
+                                               }
+                                       }
                                }
                        }
                        close(fd);
@@ -394,4 +402,130 @@ static char *makequery(const char *path, const char *uuid, const char *token)
 }
 #endif
 
+/*****************************************************************************************************************************/
+
+#include <sys/un.h>
+#include "afb-proto-ws.h"
+
+static int get_socket_unix(const char *uri)
+{
+       int fd, rc;
+       struct sockaddr_un addr;
+       size_t length;
+
+       length = strlen(uri);
+       if (length >= 108) {
+               errno = ENAMETOOLONG;
+               return -1;
+       }
+
+       fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (fd < 0)
+               return fd;
+
+       memset(&addr, 0, sizeof addr);
+       addr.sun_family = AF_UNIX;
+       strcpy(addr.sun_path, uri);
+       if (addr.sun_path[0] == '@')
+               addr.sun_path[0] = 0; /* implement abstract sockets */
+       rc = connect(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
+       if (rc < 0) {
+               close(fd);
+               return rc;
+       }
+       return fd;
+}
+
+static int get_socket_inet(const char *uri)
+{
+       int rc, fd;
+       const char *service, *host, *api;
+       struct addrinfo hint, *rai, *iai;
+
+       /* scan the uri */
+       service = strrchr(uri, ':');
+       if (service == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+       api = strchrnul(service, '/');
+       host = strndupa(uri, service++ - uri);
+       service = strndupa(service, api - service);
+
+       /* get addr */
+       memset(&hint, 0, sizeof hint);
+       hint.ai_family = AF_INET;
+       hint.ai_socktype = SOCK_STREAM;
+       rc = getaddrinfo(host, service, &hint, &rai);
+       if (rc != 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /* get the socket */
+       iai = rai;
+       while (iai != NULL) {
+               fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
+               if (fd >= 0) {
+                       rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
+                       if (rc == 0) {
+                               freeaddrinfo(rai);
+                               return fd;
+                       }
+                       close(fd);
+               }
+               iai = iai->ai_next;
+       }
+       freeaddrinfo(rai);
+       return -1;
+}
+
+static int get_socket(const char *uri)
+{
+       int fd;
+
+       /* check for unix socket */
+       if (0 == strncmp(uri, "unix:", 5))
+               /* unix socket */
+               fd = get_socket_unix(uri + 5);
+       else if (0 == strncmp(uri, "tcp:", 4))
+               /* unix socket */
+               fd = get_socket_inet(uri + 4);
+       else
+               /* inet socket */
+               fd = get_socket_inet(uri);
+
+       /* configure the socket */
+       if (fd >= 0) {
+               fcntl(fd, F_SETFD, FD_CLOEXEC);
+               fcntl(fd, F_SETFL, O_NONBLOCK);
+       }
+       return fd;
+}
+
+/*
+ * Establish a websocket-like client connection to the API of 'uri' and if successful
+ * instantiate a client afb_proto_ws websocket for this API using 'itf' and 'closure'.
+ * (see afb_proto_ws_create_client).
+ * The systemd event loop 'eloop' is used to handle the websocket.
+ * Returns NULL in case of failure with errno set appropriately.
+ */
+struct afb_proto_ws *afb_ws_client_connect_api(struct sd_event *eloop, const char *uri, struct afb_proto_ws_client_itf *itf, void *closure)
+{
+       int fd;
+       struct afb_proto_ws *pws;
+       struct fdev *fdev;
+
+       fd = get_socket(uri);
+       if (fd >= 0) {
+               fdev = fdev_systemd_create(eloop, fd);
+               if (fdev) {
+                       pws = afb_proto_ws_create_client(fdev, itf, closure);
+                       if (pws)
+                               return pws;
+               }
+               close(fd);
+       }
+       return NULL;
+}