Update copyright dates
[src/app-framework-binder.git] / src / afb-api-ws.c
index 7e0adb7..4ea8610 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, 2016, 2017 "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");
@@ -16,7 +16,6 @@
  */
 
 #define _GNU_SOURCE
-#define NO_PLUGIN_VERBOSE_MACRO
 
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 
-#include <systemd/sd-event.h>
 #include "afb-api.h"
 #include "afb-apiset.h"
-#include "afb-common.h"
+#include "afb-api-ws.h"
+#include "afb-fdev.h"
+#include "afb-socket.h"
 #include "afb-stub-ws.h"
 #include "verbose.h"
-#include "sd-fds.h"
+#include "fdev.h"
 
-struct api_ws
+struct api_ws_server
 {
-       char *path;             /* path of the object for the API */
-       char *api;              /* api name of the interface */
-       int fd;                 /* file descriptor */
-       sd_event_source *listensrc; /**< systemd source for server socket */
-       struct afb_apiset *apiset;
+       struct afb_apiset *apiset;      /* the apiset for calling */
+       struct fdev *fdev;              /* fdev handler */
+       uint16_t offapi;                /* api name of the interface */
+       char uri[];                     /* the uri of the server socket */
 };
 
+/******************************************************************************/
+/***       C L I E N T                                                      ***/
 /******************************************************************************/
 
-/*
- * create a structure api_ws not connected to the 'path'.
- */
-static struct api_ws *api_ws_make(const char *path)
-{
-       struct api_ws *api;
-       size_t length;
-
-       /* allocates the structure */
-       length = strlen(path);
-       api = calloc(1, sizeof *api + 1 + length);
-       if (api == NULL) {
-               errno = ENOMEM;
-               goto error;
-       }
-
-       /* path is copied after the struct */
-       api->path = (char*)(api+1);
-       memcpy(api->path, path, length + 1);
-
-       /* api name is at the end of the path */
-       while (length && path[length - 1] != '/' && path[length - 1] != ':')
-               length = length - 1;
-       api->api = &api->path[length];
-       if (api->api == NULL || !afb_api_is_valid_name(api->api)) {
-               errno = EINVAL;
-               goto error2;
-       }
-
-       api->fd = -1;
-       return api;
-
-error2:
-       free(api);
-error:
-       return NULL;
-}
-
-static int api_ws_socket_unix(const char *path, int server)
+static struct fdev *reopen_client(void *closure)
 {
-       int fd, rc;
-       struct sockaddr_un addr;
-       size_t length;
-
-       length = strlen(path);
-       if (length >= 108) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-
-       if (server && path[0] != '@')
-               unlink(path);
-
-       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, path);
-       if (addr.sun_path[0] == '@')
-               addr.sun_path[0] = 0; /* implement abstract sockets */
-       if (server) {
-               rc = bind(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
-       } else {
-               rc = connect(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
-       }
-       if (rc < 0) {
-               close(fd);
-               return rc;
-       }
-       return fd;
+       const char *uri = closure;
+       return afb_socket_open_fdev(uri, 0);
 }
 
-static int api_ws_socket_inet(const char *path, int server)
+int afb_api_ws_add_client(const char *uri, struct afb_apiset *declare_set, struct afb_apiset *call_set, int strong)
 {
-       int rc, fd;
-       const char *service, *host, *api;
-       struct addrinfo hint, *rai, *iai;
-
-       /* scan the uri */
-       api = strrchr(path, '/');
-       service = strrchr(path, ':');
-       if (api == NULL || service == NULL || api < service) {
-               errno = EINVAL;
-               return -1;
-       }
-       host = strndupa(path, service++ - path);
-       service = strndupa(service, api - service);
+       struct afb_stub_ws *stubws;
+       struct fdev *fdev;
+       const char *api;
 
-       /* 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) {
+       /* check the api name */
+       api = afb_socket_api(uri);
+       if (api == NULL || !afb_api_is_valid_name(api)) {
+               ERROR("invalid (too long) ws client uri %s", uri);
                errno = EINVAL;
-               return -1;
+               goto error;
        }
 
-       /* get the socket */
-       iai = rai;
-       while (iai != NULL) {
-               fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
-               if (fd >= 0) {
-                       if (server) {
-                               rc = bind(fd, iai->ai_addr, iai->ai_addrlen);
-                       } else {
-                               rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
-                       }
-                       if (rc == 0) {
-                               freeaddrinfo(rai);
-                               return fd;
+       /* open the socket */
+       fdev = afb_socket_open_fdev(uri, 0);
+       if (fdev) {
+               /* create the client stub */
+               stubws = afb_stub_ws_create_client(fdev, api, call_set);
+               if (!stubws) {
+                       ERROR("can't setup client ws service to %s", uri);
+                       fdev_unref(fdev);
+               } else {
+                       if (afb_stub_ws_client_add(stubws, declare_set) >= 0) {
+#if 1
+                               /* it is asserted here that uri is never released */
+                               afb_stub_ws_client_robustify(stubws, reopen_client, (void*)uri, NULL);
+#else
+                               /* it is asserted here that uri is released, so use a copy */
+                               afb_stub_ws_client_robustify(stubws, reopen_client, strdup(uri), free);
+#endif
+                               return 0;
                        }
-                       close(fd);
+                       ERROR("can't add the client to the apiset for service %s", uri);
+                       afb_stub_ws_unref(stubws);
                }
-               iai = iai->ai_next;
        }
-       freeaddrinfo(rai);
-       return -1;
+error:
+       return -!!strong;
 }
 
-static int api_ws_socket(const char *path, int server)
+int afb_api_ws_add_client_strong(const char *uri, struct afb_apiset *declare_set, struct afb_apiset *call_set)
 {
-       int fd, rc;
-
-       /* check for systemd socket */
-       if (0 == strncmp(path, "sd:", 3))
-               fd = sd_fds_for(path + 3);
-       else {
-               /* check for unix socket */
-               if (0 == strncmp(path, "unix:", 5))
-                       /* unix socket */
-                       fd = api_ws_socket_unix(path + 5, server);
-               else
-                       /* inet socket */
-                       fd = api_ws_socket_inet(path, server);
-
-               if (fd >= 0 && server) {
-                       rc = 1;
-                       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof rc);
-                       rc = listen(fd, 5);
-               }
-       }
-       /* configure the socket */
-       if (fd >= 0) {
-               fcntl(fd, F_SETFD, FD_CLOEXEC);
-               fcntl(fd, F_SETFL, O_NONBLOCK);
-       }
-       return fd;
+       return afb_api_ws_add_client(uri, declare_set, call_set, 1);
 }
 
-/**********************************************************************************/
-
-int afb_api_ws_add_client(const char *path, struct afb_apiset *apiset)
+int afb_api_ws_add_client_weak(const char *uri, struct afb_apiset *declare_set, struct afb_apiset *call_set)
 {
-       struct api_ws *apiws;
-       struct afb_stub_ws *stubws;
-
-       /* create the ws client api */
-       apiws = api_ws_make(path);
-       if (apiws == NULL)
-               goto error;
-
-       /* connect to the service */
-       apiws->fd = api_ws_socket(apiws->path, 0);
-       if (apiws->fd < 0) {
-               ERROR("can't connect to ws service %s", apiws->path);
-               goto error2;
-       }
-
-       stubws = afb_stub_ws_create_client(apiws->fd, apiws->api, apiset);
-       if (!stubws) {
-               ERROR("can't setup client ws service to %s", apiws->path);
-               goto error3;
-       }
-       if (afb_stub_ws_client_add(stubws, apiset) < 0) {
-               ERROR("can't add the client to the apiset for service %s", apiws->path);
-               goto error4;
-       }
-       free(apiws);
-       return 0;
-error4:
-       afb_stub_ws_unref(stubws);
-error3:
-       close(apiws->fd);
-error2:
-       free(apiws);
-error:
-       return -1;
+       return afb_api_ws_add_client(uri, declare_set, call_set, 0);
 }
 
-static int api_ws_server_accept_client(struct api_ws *apiws, int fd)
-{
-       return -!afb_stub_ws_create_server(fd, apiws->api, apiws->apiset);
-}
+/*****************************************************************************/
+/***       S E R V E R                                                      ***/
+/******************************************************************************/
 
-static void api_ws_server_accept(struct api_ws *apiws)
+static void api_ws_server_accept(struct api_ws_server *apiws)
 {
-       int rc, fd;
+       int fd;
        struct sockaddr addr;
        socklen_t lenaddr;
+       struct fdev *fdev;
+       struct afb_stub_ws *server;
 
        lenaddr = (socklen_t)sizeof addr;
-       fd = accept(apiws->fd, &addr, &lenaddr);
-       if (fd >= 0) {
-               rc = api_ws_server_accept_client(apiws, fd);
-               if (rc >= 0)
-                       return;
-               close(fd);
+       fd = accept(fdev_fd(apiws->fdev), &addr, &lenaddr);
+       if (fd < 0) {
+               ERROR("can't accept connection to %s: %m", apiws->uri);
+       } else {
+               fdev = afb_fdev_create(fd);
+               if (!fdev) {
+                       ERROR("can't hold accepted connection to %s: %m", apiws->uri);
+                       close(fd);
+               } else {
+                       server = afb_stub_ws_create_server(fdev, &apiws->uri[apiws->offapi], apiws->apiset);
+                       if (server)
+                               afb_stub_ws_set_on_hangup(server, afb_stub_ws_unref);
+                       else
+                               ERROR("can't serve accepted connection to %s: %m", apiws->uri);
+               }
        }
 }
 
-static int api_ws_server_connect(struct api_ws *apiws);
+static int api_ws_server_connect(struct api_ws_server *apiws);
 
-static int api_ws_server_listen_callback(sd_event_source *src, int fd, uint32_t revents, void *closure)
+static void api_ws_server_listen_callback(void *closure, uint32_t revents, struct fdev *fdev)
 {
-       struct api_ws *apiws = closure;
+       struct api_ws_server *apiws = closure;
 
-       if ((revents & EPOLLIN) != 0)
-               api_ws_server_accept(apiws);
        if ((revents & EPOLLHUP) != 0)
                api_ws_server_connect(apiws);
-       return 0;
+       else if ((revents & EPOLLIN) != 0)
+               api_ws_server_accept(apiws);
 }
 
-static void api_ws_server_disconnect(struct api_ws *apiws)
+static void api_ws_server_disconnect(struct api_ws_server *apiws)
 {
-       if (apiws->listensrc != NULL) {
-               sd_event_source_unref(apiws->listensrc);
-               apiws->listensrc = NULL;
-       }
-       if (apiws->fd >= 0) {
-               close(apiws->fd);
-               apiws->fd = -1;
-       }
+       fdev_unref(apiws->fdev);
+       apiws->fdev = 0;
 }
 
-static int api_ws_server_connect(struct api_ws *apiws)
+static int api_ws_server_connect(struct api_ws_server *apiws)
 {
-       int rc;
-
        /* ensure disconnected */
        api_ws_server_disconnect(apiws);
 
        /* request the service object name */
-       apiws->fd = api_ws_socket(apiws->path, 1);
-       if (apiws->fd < 0)
-               ERROR("can't create socket %s", apiws->path);
+       apiws->fdev = afb_socket_open_fdev(apiws->uri, 1);
+       if (!apiws->fdev)
+               ERROR("can't create socket %s", apiws->uri);
        else {
                /* listen for service */
-               rc = sd_event_add_io(afb_common_get_event_loop(),
-                               &apiws->listensrc, apiws->fd, EPOLLIN,
-                               api_ws_server_listen_callback, apiws);
-               if (rc >= 0)
-                       return 0;
-               close(apiws->fd);
-               errno = -rc;
-               ERROR("can't add ws object %s", apiws->path);
+               fdev_set_events(apiws->fdev, EPOLLIN);
+               fdev_set_callback(apiws->fdev, api_ws_server_listen_callback, apiws);
+               return 0;
        }
        return -1;
 }
 
 /* create the service */
-int afb_api_ws_add_server(const char *path, struct afb_apiset *apiset)
+int afb_api_ws_add_server(const char *uri, struct afb_apiset *declare_set, struct afb_apiset *call_set)
 {
        int rc;
-       struct api_ws *apiws;
+       const char *api;
+       struct api_ws_server *apiws;
+       size_t luri, lapi, extra;
+
+       /* check the size */
+       luri = strlen(uri);
+       if (luri > 4000) {
+               ERROR("can't create socket %s", uri);
+               errno = E2BIG;
+               return -1;
+       }
 
-       /* creates the ws api object */
-       apiws = api_ws_make(path);
-       if (apiws == NULL)
+       /* check the api name */
+       api = afb_socket_api(uri);
+       if (api == NULL || !afb_api_is_valid_name(api)) {
+               ERROR("invalid api name in ws uri %s", uri);
+               errno = EINVAL;
                goto error;
+       }
 
        /* check api name */
-       if (!afb_apiset_lookup(apiset, apiws->api, 1)) {
-               ERROR("Can't provide ws-server for %s: API %s doesn't exist", path, apiws->api);
-               goto error2;
+       if (!afb_apiset_lookup(call_set, api, 1)) {
+               ERROR("Can't provide ws-server for URI %s: API %s doesn't exist", uri, api);
+               errno = ENOENT;
+               goto error;
+       }
+
+       /* make the structure */
+       lapi = strlen(api);
+       extra = luri == (api - uri) + lapi ? 0 : lapi + 1;
+       apiws = malloc(sizeof * apiws + 1 + luri + extra);
+       if (!apiws) {
+               ERROR("out of memory");
+               errno = ENOMEM;
+               goto error;
+       }
+
+       apiws->apiset = afb_apiset_addref(call_set);
+       apiws->fdev = 0;
+       strcpy(apiws->uri, uri);
+       if (!extra)
+               apiws->offapi = (uint16_t)(api - uri);
+       else {
+               apiws->offapi = (uint16_t)(luri + 1);
+               strcpy(&apiws->uri[apiws->offapi], api);
        }
 
        /* connect for serving */
        rc = api_ws_server_connect(apiws);
-       if (rc < 0)
-               goto error2;
-
-       apiws->apiset = afb_apiset_addref(apiset);
-       return 0;
+       if (rc >= 0)
+               return 0;
 
-error2:
+       afb_apiset_unref(apiws->apiset);
        free(apiws);
 error:
        return -1;
 }
-
-