/*
- * 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");
*/
#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)
+static struct fdev *reopen_client(void *closure)
{
- 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;
+ const char *uri = closure;
+ return afb_socket_open_fdev(uri, 0);
}
-static int api_ws_socket_unix(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 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;
-}
-
-static int api_ws_socket_inet(const char *path, int server)
-{
- 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);
+ /* 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;
}
- if (rc == 0) {
- freeaddrinfo(rai);
- return fd;
- }
- 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;
- }
- free(apiws);
- return 0;
-
-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(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;
}
-
-