diff --git a/.travis.yml b/.travis.yml index f021b2e..994f20a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,6 @@ env: - DISTRIB_ID=ubuntu VARIANT=coverage MARCH=x86_64 CHECK_TARGET=check - DISTRIB_ID=ubuntu VARIANT=release MARCH=x86_64 CHECK_TARGET=memcheck - DISTRIB_ID=ubuntu VARIANT=debug MARCH=arm32v7 CHECK_TARGET=check - - DISTRIB_ID=ubuntu VARIANT=minsize MARCH=arm32v7 CHECK_TARGET=check - DISTRIB_ID=alpine VARIANT=debug MARCH=x86_64 CHECK_TARGET=check before_script: - make -j4 DISTRIB_ID=$DISTRIB_ID VARIANT=$VARIANT MARCH=$MARCH diff --git a/changelog.md b/changelog.md index 51379dc..a15b4a4 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,12 @@ ### Breaking Changes * Remove CMake support (change build system to meson) +* Make argument credentials const in `wf_authenticate_fn` + +### New Features + +* Add adapter client implementation +* Allow system to choose port of webfuse server (by setting port in `wf_server_config` to 0) ### Fixes diff --git a/doc/api.md b/doc/api.md index 6cd8340..0c862d2 100644 --- a/doc/api.md +++ b/doc/api.md @@ -5,9 +5,10 @@ Please refer to the [build instructions](build.md) to generate API reference doc ## Contents -- [Authentication](#Authentication) +- [Authentication](#Authentication (Adapter Server)) +- [Adapter Client](#Adapter Client) -## Authentication +## Authentication (Adapter Server) By default, webfuse daemon will redirect each filesystem call to the first connected provider without any authentication. This might be good for testing purposes or when an external authentication mechanism is used. In some use cases, explicit authentication is needed. Therefore, authentication can be enabled within webfuse daemon. @@ -53,3 +54,107 @@ The authenticator type **username** is used to authenticate via username and pas - **password** refers to the password of the user **Note** that no further encryption is done, so this authenticator type should not be used over unencrypted websocket connections. + +## Adapter Client + +Webfuse also supports a client version of an adapter. This might be useful +to connect to a cloud based provider server and request a filesystem. + +The adapter client is driven by a callback function, which is triggered whenever +an event occurs, an adapter should take care of. + + static void client_callback( + struct wf_client * client, + int reason, + void * arg) + { + switch (reason) + { + // ... handle events + default: + break; + } + } + + // ... + void * user_data = ... + struct wf_client * client = wf_client_create(&client_callback, user_data); + +### Init and Cleanup + +There are two events definied to handle init and cleanup of clients: + +- `WF_CLIENT_INIT` +- `WF_CLIENT_CLEANUP` + +These two are the outer-most events and can be used for custom initialization +and cleanup. + +A thrid event, `WF_CLIENT_CREATED`, is triggered, when the client is fully created. +You might use this event to connect to a foreign provider. + +### Connection Status + +The connection status is relected by two events: + +- `WF_CLIENT_CONNECTED` +- `WF_CLIENT_DISCONNECTED` + +The disconnected event is also triggerd, when an attempt to connect fails. + +### Transport Layer Security + +During startup, the event `WF_CLIENT_GET_TLS_CONFIG` is triggered. +In this case, the `arg` parameter points to an instance of `struct wf_client_tlsconfig`. + +To enable TLS, set this struct. If the callback is ignorted or the struct is not +set, TLS is not active. + + static void client_callback( + struct wf_client * client, + int reason, + void * arg) + { + switch (reason) + { + // ... + case WF_CLIENT_GET_TLS_CONFIG: + { + struct wf_client_tlsconfig * tls = arg; + wf_client_tslconfig_set_keypath(tls, "/path/to/key.pem"); + wf_client_tslconfig_set_certpath(tls, "/path/to/cert.pem"); + wf_client_tslconfig_set_cafilepath(tls, "/path/to/ca_file.pem"); + } + break; + default: + break; + } + } + +### Authentication (Adapter Client) + +During `wf_client_authenticate` the event `WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS` +is triggered to query credentials for authentication. + +In this case, the `arg` paramter point to an instance of `struct wf_credentials`. + + static void client_callback( + struct wf_client * client, + int reason, + void * arg) + { + switch (reason) + { + // ... + case WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS: + { + struct wf_credentials * creds = arg; + wf_credentials_set_type(creds, "username"); + wf_credentials_add(creds, "username", "Bob"); + wf_credentials_add(creds, "password", "secret"); + } + break; + default: + break; + } + } diff --git a/doc/protocol.md b/doc/protocol.md index 32eaa9d..7c686e4 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -221,14 +221,20 @@ Read from an open file. | "identiy" | Use data as is; note that JSON strings are UTF-8 encoded | | "base64" | data is base64 encoded | -## Requests (Provider -> Adapter) +## Requests (Client -> Server) + +_Note:_ The following requests are initiated by the client and +responded by the server. Depending on the use case, a provider +can be a client (e.g. a webbrowser which want's to inject a +filesystem to an IoT device) or a server (e.g. a cloud based +service, an adapter connects to and requests a filesystem). ### add_filesystem Adds a filesystem. - fs provider: {"method": "add_filesystem", "params": [], "id": } - webfuse daemon: {"result": {"id": }, "id": } + client: {"method": "add_filesystem", "params": [], "id": } + server: {"result": {"id": }, "id": } | Item | Data type | Description | | ----------- | ----------| ------------------------------- | @@ -237,10 +243,10 @@ Adds a filesystem. ### authtenticate Authenticate the provider. -If authentication is enabled, a provider must be authenticated by the adapter before filesystems can be added. +If authentication is enabled, a client must be authenticated by the server before filesystems can be added. - fs provider: {"method": "authenticate", "params": [, ], "id": } - webfuse daemon: {"result": {}, "id": } + client: {"method": "authenticate", "params": [, ], "id": } + server: {"result": {}, "id": } | Item | Data type | Description | | ----------- | ----------| ------------------------------- | diff --git a/include/webfuse/adapter/authenticate.h b/include/webfuse/adapter/authenticate.h index e11e300..0146768 100644 --- a/include/webfuse/adapter/authenticate.h +++ b/include/webfuse/adapter/authenticate.h @@ -31,7 +31,7 @@ struct wf_credentials; /// \see wf_server_protocol_add_authenticator //------------------------------------------------------------------------------ typedef bool wf_authenticate_fn( - struct wf_credentials * credentials, + struct wf_credentials const * credentials, void * user_data); #ifdef __cplusplus diff --git a/include/webfuse/adapter/client.h b/include/webfuse/adapter/client.h new file mode 100644 index 0000000..29b4f17 --- /dev/null +++ b/include/webfuse/adapter/client.h @@ -0,0 +1,159 @@ +//////////////////////////////////////////////////////////////////////////////// +/// \file adapter/client.h +/// \brief Adapter client. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef WF_ADAPTER_CLIENT_H +#define WF_ADAPTER_CLIENT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +//------------------------------------------------------------------------------ +/// \struct wf_client +/// \brief Adapter client +/// +/// An adapter client is used to connect to a remote provider server, e.g. a +/// cloud service, an request a filesystem to inject. +//------------------------------------------------------------------------------ +struct wf_client; + +//------------------------------------------------------------------------------ +/// \brief Created a new instance of an adapter client. +/// +/// During creation, the client is initialized in the following order: +/// - WF_CLIENT_INIT is triggered to initialize custom data +/// - WF_CLIENT_GET_TLS_CONFIG is triggered to query TLS configuration +/// - internal initialization is performed +/// - WF_CLIENT_CREATED is triggered +/// +/// Therefore, the client should not be used, unless WF_CLIENT_CREATED is +/// triggered. +/// +/// When TLS configuration is queried, a pointer to an instance if +/// \see wf_client_tlsconfig is provided to be set by the user. +/// +/// \param callback Pointer to the callback function. +/// \param user_data Pointer to user data. +/// +/// \return Newly created instance of an adapter client. +//------------------------------------------------------------------------------ +extern WF_API struct wf_client * +wf_client_create( + wf_client_callback_fn * callback, + void * user_data); + +//------------------------------------------------------------------------------ +/// \brief Disposes an adapter client. +/// +/// \param client Pointer to the client. +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_dispose( + struct wf_client * client); + +//------------------------------------------------------------------------------ +/// \brief Get user data. +/// +/// \param client Pointer to the client. +//------------------------------------------------------------------------------ +extern WF_API void * +wf_client_get_userdata( + struct wf_client * client); + +//------------------------------------------------------------------------------ +/// \brief Triggers the client. +/// +/// \param client Pointer to the client. +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_service( + struct wf_client * client); + +//------------------------------------------------------------------------------ +/// \brief Interrupts a service call. +/// +/// Wakes up the client. +/// +/// \note This is the only function that can be used safely from another +// thread. +/// +/// \param client Pointer to the client. +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_interrupt( + struct wf_client * client); + +//------------------------------------------------------------------------------ +/// \brief Connects to a foreign server. +/// +/// Starts to connect. The callback is triggered, when connect is finised: +/// - WF_CLIENT_CONNECTED on success +/// - WF_CLIENT_DISCONNECTED on connect error +/// +/// \param client Pointer to the client. +/// \param url URL of the remote ppovider. +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_connect( + struct wf_client * client, + char const * url); + +//------------------------------------------------------------------------------ +/// \brief Disconnects from the foreign server. +/// +/// The event WF_CLIENT_DISCONNECTED is triggered, when disconnected. +/// +/// \param client Pointer to the client. +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_disconnect( + struct wf_client * client); + +//------------------------------------------------------------------------------ +/// \brief Authenticated the client. +/// +/// During authentication, credentials are queries via +/// WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS event. In that case a pointer to an +/// instance of wf_client_tlsconfig is provided to set credentials. +/// +/// When authentications finishes, an event is triggered: +/// - WF_CLIENT_AUTHENTICATED on success +/// - WF_CLIENT_AUTHENTICATION_FAILED on failure +/// +/// \param client Pointer to the client. +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_authenticate( + struct wf_client * client); + +//------------------------------------------------------------------------------ +/// \brief Add a filesystem. +/// +/// After add filesystem finished, an event is triggered: +/// - WF_CLIENT_FILESYSTEM_ADDED on success +/// - WF_CLIENT_FILESYSTEM_ADD_FAILED on failure +/// +/// \note Currently, only one file system is supported by the client. +/// +/// \param client Pointer to the client. +/// \param local_path Local path where the filesystem should be places +/// (must be an exististing and empty directory). +/// \param name Name of the filesystem (identifier sent to the provider). +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_add_filesystem( + struct wf_client * client, + char const * local_path, + char const * name); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/webfuse/adapter/client_callback.h b/include/webfuse/adapter/client_callback.h new file mode 100644 index 0000000..ad8bd86 --- /dev/null +++ b/include/webfuse/adapter/client_callback.h @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////////// +/// \file adapter/client_callbak.h +/// \brief Callback of adapter clients. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef WF_ADAPTER_CLIENT_CALLBACK_H +#define WF_ADAPTER_CLIENT_CALLBACK_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#define WF_CLIENT_INIT 0x0001 ///< Out-most initialization of a client +#define WF_CLIENT_CLEANUP 0x0002 ///< Out-most cleanup of a client +#define WF_CLIENT_CREATED 0x0003 ///< Client is fully initialized an can be used + +#define WF_CLIENT_CONNECTED 0x0011 ///< Connection to a foreign provider established +#define WF_CLIENT_DISCONNECTED 0x0012 ///< Connection closed or connect failed + +#define WF_CLIENT_AUTHENTICATED 0x0021 ///< Authentication succeeded +#define WF_CLIENT_AUTHENTICATION_FAILED 0x0022 ///< Authentication failed +#define WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS 0x0023 ///< Query credentials (\see wf_client_authenticate) + +#define WF_CLIENT_FILESYSTEM_ADDED 0x0031 ///< File system added successfully +#define WF_CLIENT_FILESYSTEM_ADD_FAILED 0x0032 ///< Failed to add file system + +#define WF_CLIENT_GET_TLS_CONFIG 0x0041 ///< Query TLS config (\see wf_client_create) + +struct wf_client; + +//------------------------------------------------------------------------------ +/// \brief Client callback function +/// +/// This function is triggered whenever an event occurs the client should +/// be aware of. +/// +/// \param client Pointer to the client +/// \param reason Event, that triggered the callback +/// \param args Event-specific arguments +//------------------------------------------------------------------------------ +typedef void wf_client_callback_fn( + struct wf_client * client, + int reason, + void * args); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/include/webfuse/adapter/client_tlsconfig.h b/include/webfuse/adapter/client_tlsconfig.h new file mode 100644 index 0000000..550b664 --- /dev/null +++ b/include/webfuse/adapter/client_tlsconfig.h @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +/// \file adapter/client_tslconfig.h +/// \brief Configuration of TLS (Transport Layer Security) for adapter clients. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef WF_ADAPTER_CLIENT_TLSCONFIG_H +#define WF_ADAPTER_CLIENT_TLSCONFIG_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +//------------------------------------------------------------------------------ +/// \struct wf_client_tlsconfig +/// \brief TLS configuration of the client. +/// +/// TLS configuration is queried during initialization of a client via +/// WF_CLIENT_GET_TLS_CONFIG event. +/// +/// \see WF_CLIENT_GET_TLS_CONFIG +/// \see wf_client_create +//------------------------------------------------------------------------------ +struct wf_client_tlsconfig; + +//------------------------------------------------------------------------------ +/// \brief Sets the path to the private key. +/// +/// \note To enable TLS both, key_path and cert_path, must be specified. +/// +/// \param config Pointer to config. +/// \param key_path Path to private key file (in PEM format). +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_tlsconfig_set_keypath( + struct wf_client_tlsconfig * config, + char const * key_path); + +//------------------------------------------------------------------------------ +/// \brief Sets the path to the clients certificate. +/// +/// \note To enable TLS both, key_path and cert_path, must be specified. +/// +/// \param config Pointer to the config. +/// \param cert_path Path the the clients certificate (in PEM format). +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_tlsconfig_set_certpath( + struct wf_client_tlsconfig * config, + char const * cert_path); + +//------------------------------------------------------------------------------ +/// \brief Sets the path the CA file. +/// +/// \param config Pointer to the config. +/// \param cafile_path Path to CA file (in PEM format). +//------------------------------------------------------------------------------ +extern WF_API void +wf_client_tlsconfig_set_cafilepath( + struct wf_client_tlsconfig * config, + char const * cafile_path); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/webfuse/adapter/credentials.h b/include/webfuse/adapter/credentials.h index e3e65fc..ca404f7 100644 --- a/include/webfuse/adapter/credentials.h +++ b/include/webfuse/adapter/credentials.h @@ -65,6 +65,28 @@ extern WF_API char const * wf_credentials_get( struct wf_credentials const * credentials, char const * key); +//------------------------------------------------------------------------------ +/// \brief Sets the type of the credentials. +/// +/// \param credentials Pointer to credentials object. +/// \param type Type of credentials. +//------------------------------------------------------------------------------ +extern WF_API void wf_credentials_set_type( + struct wf_credentials * credentials, + char const * type); + +//------------------------------------------------------------------------------ +/// \brief Adds an item to credentials +/// +/// \param credentials Pointer to credentials object. +/// \param key String to identify the item. +/// \param key Value of the item. +//------------------------------------------------------------------------------ +extern WF_API void wf_credentials_add( + struct wf_credentials * credentials, + char const * key, + char const * value); + #ifdef __cplusplus } #endif diff --git a/include/webfuse/adapter/server.h b/include/webfuse/adapter/server.h index f46f3ef..dabfedd 100644 --- a/include/webfuse/adapter/server.h +++ b/include/webfuse/adapter/server.h @@ -65,6 +65,20 @@ extern WF_API void wf_server_service( extern WF_API void wf_server_interrupt( struct wf_server * server); +//------------------------------------------------------------------------------ +/// \brief Returns the port number used by the server +/// +/// This function can be used to determine the port number of the server, +/// if it was configured to let the system choose a free port. +// +/// \param server pointer to server +/// \return Port number used by the server. +/// +/// \see wf_server_config_set_port +//------------------------------------------------------------------------------ +extern WF_API int wf_server_get_port( + struct wf_server const * server); + #ifdef __cplusplus } #endif diff --git a/include/webfuse/adapter/server_config.h b/include/webfuse/adapter/server_config.h index e140c4d..6e0bfbb 100644 --- a/include/webfuse/adapter/server_config.h +++ b/include/webfuse/adapter/server_config.h @@ -116,6 +116,8 @@ extern WF_API void wf_server_config_set_vhostname( //------------------------------------------------------------------------------ /// \brief Sets the port number of the websockets server. /// +/// Note: Set port number to 0 to let system choose a free port. +/// /// \param config pointer of configuration object /// \param port port number of the websockets server //------------------------------------------------------------------------------ diff --git a/include/webfuse/core/protocol_names.h b/include/webfuse/core/protocol_names.h index 4de8c4a..aa9abfe 100644 --- a/include/webfuse/core/protocol_names.h +++ b/include/webfuse/core/protocol_names.h @@ -12,10 +12,22 @@ //------------------------------------------------------------------------------ #define WF_PROTOCOL_NAME_ADAPTER_SERVER ("webfuse-adapter-server") +//------------------------------------------------------------------------------ +/// \def WF_PROTOCOL_NAME_ADAPTER_CLIENT +/// \brief Name of the websocket protocol an adapter client is running. +//------------------------------------------------------------------------------ +#define WF_PROTOCOL_NAME_ADAPTER_CLIENT ("webfuse-adapter-client") + //------------------------------------------------------------------------------ /// \def WF_PROTOCOL_NAME_PROVIDER_CLIENT /// \brief Name of the websocket protocol an provider client is running. //------------------------------------------------------------------------------ #define WF_PROTOCOL_NAME_PROVIDER_CLIENT ("webfuse-provider-client") +//------------------------------------------------------------------------------ +/// \def WF_PROTOCOL_NAME_PROVIDER_SERVER +/// \brief Name of the websocket protocol an provider server is running. +//------------------------------------------------------------------------------ +#define WF_PROTOCOL_NAME_PROVIDER_SERVER ("webfuse-provider-server") + #endif diff --git a/include/webfuse_adapter.h b/include/webfuse_adapter.h index 1ff048f..093fd06 100644 --- a/include/webfuse_adapter.h +++ b/include/webfuse_adapter.h @@ -17,4 +17,9 @@ #include #include +#include +#include +#include + + #endif diff --git a/lib/webfuse/adapter/api.c b/lib/webfuse/adapter/api.c index 5049e16..38b38c1 100644 --- a/lib/webfuse/adapter/api.c +++ b/lib/webfuse/adapter/api.c @@ -8,6 +8,9 @@ #include "webfuse/core/util.h" +#include "webfuse/adapter/impl/client.h" +#include "webfuse/adapter/impl/client_tlsconfig.h" + // server struct wf_server * wf_server_create( @@ -34,6 +37,11 @@ void wf_server_interrupt( wf_impl_server_interrupt(server); } +int wf_server_get_port( + struct wf_server const * server) +{ + return wf_impl_server_get_port(server); +} // server protocol @@ -147,6 +155,21 @@ char const * wf_credentials_get( return wf_impl_credentials_get(credentials, key); } +void wf_credentials_set_type( + struct wf_credentials * credentials, + char const * type) +{ + wf_impl_credentials_set_type(credentials, type); +} + +void wf_credentials_add( + struct wf_credentials * credentials, + char const * key, + char const * value) +{ + wf_impl_credentials_add(credentials, key, value); +} + // mountpoint struct wf_mountpoint * @@ -178,3 +201,98 @@ wf_mountpoint_set_userdata( { wf_impl_mountpoint_set_userdata(mountpoint, user_data, dispose); } + +// client + +struct wf_client * +wf_client_create( + wf_client_callback_fn * callback, + void * user_data) +{ + return wf_impl_client_create(callback, user_data); +} + +void +wf_client_dispose( + struct wf_client * client) +{ + wf_impl_client_dispose(client); +} + +void * +wf_client_get_userdata( + struct wf_client * client) +{ + return wf_impl_client_get_userdata(client); +} + +void +wf_client_service( + struct wf_client * client) +{ + wf_impl_client_service(client); +} + +void +wf_client_interrupt( + struct wf_client * client) +{ + wf_impl_client_interrupt(client); +} + +void +wf_client_connect( + struct wf_client * client, + char const * url) +{ + wf_impl_client_connect(client, url); +} + +void +wf_client_disconnect( + struct wf_client * client) +{ + wf_impl_client_disconnect(client); +} + +void +wf_client_authenticate( + struct wf_client * client) +{ + wf_impl_client_authenticate(client); +} + +void +wf_client_add_filesystem( + struct wf_client * client, + char const * local_path, + char const * name) +{ + wf_impl_client_add_filesystem(client, local_path, name); +} + +// client_tlsconfig + +void +wf_client_tlsconfig_set_keypath( + struct wf_client_tlsconfig * config, + char const * key_path) +{ + wf_impl_client_tlsconfig_set_keypath(config, key_path); +} + +void +wf_client_tlsconfig_set_certpath( + struct wf_client_tlsconfig * config, + char const * cert_path) +{ + wf_impl_client_tlsconfig_set_certpath(config, cert_path); +} + +void +wf_client_tlsconfig_set_cafilepath( + struct wf_client_tlsconfig * config, + char const * cafile_path) +{ + wf_impl_client_tlsconfig_set_cafilepath(config, cafile_path); +} diff --git a/lib/webfuse/adapter/impl/client.c b/lib/webfuse/adapter/impl/client.c new file mode 100644 index 0000000..745250f --- /dev/null +++ b/lib/webfuse/adapter/impl/client.c @@ -0,0 +1,127 @@ +#include "webfuse/adapter/impl/client.h" +#include "webfuse/adapter/impl/client_protocol.h" +#include "webfuse/adapter/impl/client_tlsconfig.h" +#include "webfuse/core/lws_log.h" + +#include + +#include +#include + +#define WF_CLIENT_PROTOCOL_COUNT 2 + +struct wf_client +{ + struct wf_client_protocol protocol; + struct lws_context_creation_info info; + struct lws_protocols protocols[WF_CLIENT_PROTOCOL_COUNT]; + struct wf_client_tlsconfig tls; + struct lws_context * context; + void * user_data; +}; + +struct wf_client * +wf_impl_client_create( + wf_client_callback_fn * callback, + void * user_data) +{ + wf_lwslog_disable(); + + struct wf_client * client = malloc(sizeof(struct wf_client)); + wf_impl_client_tlsconfig_init(&client->tls); + client->user_data = user_data; + wf_impl_client_protocol_init(&client->protocol, + (wf_client_protocol_callback_fn*) callback, (void*) client); + + memset(client->protocols, 0, sizeof(struct lws_protocols) * WF_CLIENT_PROTOCOL_COUNT); + wf_impl_client_protocol_init_lws(&client->protocol, &client->protocols[0]); + + memset(&client->info, 0, sizeof(struct lws_context_creation_info)); + client->info.port = CONTEXT_PORT_NO_LISTEN; + client->info.protocols = client->protocols; + client->info.uid = -1; + client->info.gid = -1; + + wf_impl_client_protocol_callback(&client->protocol, WF_CLIENT_GET_TLS_CONFIG, &client->tls); + if (wf_impl_client_tlsconfig_isset(&client->tls)) + { + client->info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + } + + client->context = lws_create_context(&client->info); + + if (wf_impl_client_tlsconfig_isset(&client->tls)) + { + struct lws_vhost * vhost = lws_create_vhost(client->context, &client->info); + client->info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + client->info.client_ssl_cert_filepath = client->tls.cert_path; + client->info.client_ssl_private_key_filepath = client->tls.key_path; + client->info.client_ssl_ca_filepath = client->tls.cafile_path; + lws_init_vhost_client_ssl(&client->info, vhost); + } + + wf_impl_client_protocol_callback(&client->protocol, WF_CLIENT_CREATED ,NULL); + return client; +} + +void +wf_impl_client_dispose( + struct wf_client * client) +{ + lws_context_destroy(client->context); + wf_impl_client_protocol_cleanup(&client->protocol); + wf_impl_client_tlsconfig_cleanup(&client->tls); + free(client); +} + +void * +wf_impl_client_get_userdata( + struct wf_client * client) +{ + return client->user_data; +} + +void +wf_impl_client_service( + struct wf_client * client) +{ + lws_service(client->context, 0); +} + +void +wf_impl_client_interrupt( + struct wf_client * client) +{ + lws_cancel_service(client->context); +} + +void +wf_impl_client_connect( + struct wf_client * client, + char const * url) +{ + wf_impl_client_protocol_connect(&client->protocol, client->context, url); +} + +void +wf_impl_client_disconnect( + struct wf_client * client) +{ + wf_impl_client_protocol_disconnect(&client->protocol); +} + +void +wf_impl_client_authenticate( + struct wf_client * client) +{ + wf_impl_client_protocol_authenticate(&client->protocol); +} + +void +wf_impl_client_add_filesystem( + struct wf_client * client, + char const * local_path, + char const * name) +{ + wf_impl_client_protocol_add_filesystem(&client->protocol, local_path, name); +} diff --git a/lib/webfuse/adapter/impl/client.h b/lib/webfuse/adapter/impl/client.h new file mode 100644 index 0000000..62d1299 --- /dev/null +++ b/lib/webfuse/adapter/impl/client.h @@ -0,0 +1,57 @@ +#ifndef WF_ADAPTER_IMPL_CLIENT_H +#define WF_ADAPTER_IMPL_CLIENT_H + + +#include "webfuse/adapter/client_callback.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern struct wf_client * +wf_impl_client_create( + wf_client_callback_fn * callback, + void * user_data); + +extern void +wf_impl_client_dispose( + struct wf_client * client); + +extern void * +wf_impl_client_get_userdata( + struct wf_client * client); + +extern void +wf_impl_client_service( + struct wf_client * client); + +extern void +wf_impl_client_interrupt( + struct wf_client * client); + +extern void +wf_impl_client_connect( + struct wf_client * client, + char const * url); + +extern void +wf_impl_client_disconnect( + struct wf_client * client); + +extern void +wf_impl_client_authenticate( + struct wf_client * client); + +extern void +wf_impl_client_add_filesystem( + struct wf_client * client, + char const * local_path, + char const * name); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/webfuse/adapter/impl/client_protocol.c b/lib/webfuse/adapter/impl/client_protocol.c new file mode 100644 index 0000000..356cf35 --- /dev/null +++ b/lib/webfuse/adapter/impl/client_protocol.c @@ -0,0 +1,336 @@ +#include "webfuse/adapter/impl/client_protocol.h" +#include "webfuse/adapter/client_callback.h" +#include "webfuse/adapter/impl/credentials.h" +#include "webfuse/adapter/impl/filesystem.h" +#include "webfuse/adapter/impl/mountpoint.h" +#include "webfuse/core/protocol_names.h" +#include "webfuse/core/url.h" +#include "webfuse/core/util.h" +#include "webfuse/core/timer/manager.h" +#include "webfuse/core/jsonrpc/response.h" +#include "webfuse/core/jsonrpc/proxy.h" + +#include "webfuse/core/message.h" +#include "webfuse/core/message_queue.h" +#include "webfuse/core/container_of.h" + + +#include +#include + +#define WF_DEFAULT_TIMEOUT (10 * 1000) + +struct wf_impl_client_protocol_add_filesystem_context +{ + struct wf_client_protocol * protocol; + char * local_path; +}; + +static void +wf_impl_client_protocol_process( + struct wf_client_protocol * protocol, + char const * data, + size_t length) +{ + json_t * message = json_loadb(data, length, 0, NULL); + if (NULL != message) + { + if (wf_jsonrpc_is_response(message)) + { + wf_jsonrpc_proxy_onresult(protocol->proxy, message); + } + + json_decref(message); + } +} + +static bool +wf_impl_client_protocol_send( + json_t * request, + void * user_data) +{ + bool result = false; + struct wf_client_protocol * protocol = user_data; + + if (NULL != protocol->wsi) + { + struct wf_message * message = wf_message_create(request); + if (NULL != message) + { + wf_slist_append(&protocol->messages, &message->item); + lws_callback_on_writable(protocol->wsi); + result = true; + } + } + + return result; +} + + +static void +wf_impl_client_protocol_on_authenticate_finished( + void * user_data, + json_t const * result, + json_t const * WF_UNUSED_PARAM(error)) +{ + struct wf_client_protocol * protocol = user_data; + int const reason = (NULL != result) ? WF_CLIENT_AUTHENTICATED : WF_CLIENT_AUTHENTICATION_FAILED; + + protocol->callback(protocol->user_data, reason, NULL); +} + +static void +wf_impl_client_protocol_on_add_filesystem_finished( + void * user_data, + json_t const * result, + json_t const * WF_UNUSED_PARAM(error)) +{ + struct wf_impl_client_protocol_add_filesystem_context * context = user_data; + struct wf_client_protocol * protocol = context->protocol; + + int reason = WF_CLIENT_FILESYSTEM_ADD_FAILED; + if (NULL == protocol->filesystem) + { + json_t * id = json_object_get(result, "id"); + if (json_is_string(id)) + { + char const * name = json_string_value(id); + struct wf_mountpoint * mountpoint = wf_mountpoint_create(context->local_path); + protocol->filesystem = wf_impl_filesystem_create(protocol->wsi,protocol->proxy, name, mountpoint); + if (NULL != protocol->filesystem) + { + reason = WF_CLIENT_FILESYSTEM_ADDED; + } + else + { + wf_mountpoint_dispose(mountpoint); + } + } + } + + free(context->local_path); + free(context); + protocol->callback(protocol->user_data, reason, NULL); +} + +static int wf_impl_client_protocol_lws_callback( + struct lws * wsi, + enum lws_callback_reasons reason, + void * WF_UNUSED_PARAM(user), + void * in, + size_t WF_UNUSED_PARAM(len)) +{ + int result = 0; + struct lws_protocols const * ws_protocol = lws_get_protocol(wsi); + struct wf_client_protocol * protocol = (NULL != ws_protocol) ? ws_protocol->user : NULL; + + if (NULL != protocol) + { + wf_timer_manager_check(protocol->timer_manager); + + switch (reason) + { + case LWS_CALLBACK_CLIENT_ESTABLISHED: + protocol->is_connected = true; + protocol->callback(protocol->user_data, WF_CLIENT_CONNECTED, NULL); + break; + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + protocol->is_connected = false; + protocol->wsi = NULL; + protocol->callback(protocol->user_data, WF_CLIENT_DISCONNECTED, NULL); + break; + case LWS_CALLBACK_CLIENT_CLOSED: + protocol->is_connected = false; + protocol->wsi = NULL; + protocol->callback(protocol->user_data, WF_CLIENT_DISCONNECTED, NULL); + break; + case LWS_CALLBACK_CLIENT_RECEIVE: + wf_impl_client_protocol_process(protocol, in, len); + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + // fall-through + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (wsi == protocol->wsi) + { + if (protocol->is_shutdown_requested) + { + result = 1; + } + else if (!wf_slist_empty(&protocol->messages)) + { + struct wf_slist_item * item = wf_slist_remove_first(&protocol->messages); + struct wf_message * message = wf_container_of(item, struct wf_message, item); + lws_write(wsi, (unsigned char*) message->data, message->length, LWS_WRITE_TEXT); + wf_message_dispose(message); + + if (!wf_slist_empty(&protocol->messages)) + { + lws_callback_on_writable(wsi); + } + } + } + break; + case LWS_CALLBACK_RAW_RX_FILE: + if ((NULL != protocol->filesystem) && (wsi == protocol->filesystem->wsi)) + { + wf_impl_filesystem_process_request(protocol->filesystem); + } + default: + break; + } + } + + return result; +} + +void +wf_impl_client_protocol_init( + struct wf_client_protocol * protocol, + wf_client_protocol_callback_fn * callback, + void * user_data) +{ + protocol->is_connected = false, + protocol->is_shutdown_requested = false; + protocol->wsi = NULL; + protocol->callback = callback; + protocol->user_data = user_data; + protocol->filesystem = NULL; + + wf_slist_init(&protocol->messages); + protocol->timer_manager = wf_timer_manager_create(); + protocol->proxy = wf_jsonrpc_proxy_create(protocol->timer_manager, WF_DEFAULT_TIMEOUT, &wf_impl_client_protocol_send, protocol); + + protocol->callback(protocol->user_data, WF_CLIENT_INIT, NULL); +} + +void +wf_impl_client_protocol_cleanup( + struct wf_client_protocol * protocol) +{ + protocol->callback(protocol->user_data, WF_CLIENT_CLEANUP, NULL); + + wf_jsonrpc_proxy_dispose(protocol->proxy); + wf_timer_manager_dispose(protocol->timer_manager); + wf_message_queue_cleanup(&protocol->messages); + + if (NULL != protocol->filesystem) + { + wf_impl_filesystem_dispose(protocol->filesystem); + protocol->filesystem = NULL; + } +} + +void +wf_impl_client_protocol_callback( + struct wf_client_protocol * protocol, + int reason, + void * arg) +{ + protocol->callback(protocol->user_data, reason, arg); +} + +void +wf_impl_client_protocol_init_lws( + struct wf_client_protocol * protocol, + struct lws_protocols * lws_protocol) +{ + lws_protocol->name = WF_PROTOCOL_NAME_ADAPTER_CLIENT; + lws_protocol->callback = &wf_impl_client_protocol_lws_callback; + lws_protocol->per_session_data_size = 0; + lws_protocol->user = protocol; +} + +void +wf_impl_client_protocol_connect( + struct wf_client_protocol * protocol, + struct lws_context * context, + char const * url) +{ + struct wf_url url_data; + bool const success = wf_url_init(&url_data, url); + if (success) + { + struct lws_client_connect_info info; + memset(&info, 0 ,sizeof(struct lws_client_connect_info)); + info.context = context; + info.port = url_data.port; + info.address = url_data.host; + info.path = url_data.path; + info.host = info.address; + info.origin = info.address; + info.ssl_connection = (url_data.use_tls) ? LCCSCF_USE_SSL : 0; + info.protocol = WF_PROTOCOL_NAME_PROVIDER_SERVER; + info.local_protocol_name = WF_PROTOCOL_NAME_ADAPTER_CLIENT; + info.pwsi = &protocol->wsi; + + lws_client_connect_via_info(&info); + wf_url_cleanup(&url_data); + } + else + { + protocol->callback(protocol->user_data, WF_CLIENT_DISCONNECTED, NULL); + } +} + +void +wf_impl_client_protocol_disconnect( + struct wf_client_protocol * protocol) +{ + if (protocol->is_connected) + { + protocol->is_shutdown_requested = true; + lws_callback_on_writable(protocol->wsi); + } + else + { + protocol->callback(protocol->user_data, WF_CLIENT_DISCONNECTED, NULL); + } + +} + +void +wf_impl_client_protocol_authenticate( + struct wf_client_protocol * protocol) +{ + struct wf_credentials creds; + wf_impl_credentials_init_default(&creds); + protocol->callback(protocol->user_data, WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS, &creds); + + json_incref(creds.data); + wf_jsonrpc_proxy_invoke( + protocol->proxy, + &wf_impl_client_protocol_on_authenticate_finished, + protocol, + "authenticate", + "sj", + creds.type, creds.data); + + wf_impl_credentials_cleanup(&creds); +} + +void +wf_impl_client_protocol_add_filesystem( + struct wf_client_protocol * protocol, + char const * local_path, + char const * name) +{ + if (NULL == protocol->filesystem) + { + struct wf_impl_client_protocol_add_filesystem_context * context = malloc(sizeof(struct wf_impl_client_protocol_add_filesystem_context)); + context->protocol = protocol; + context->local_path = strdup(local_path); + + wf_jsonrpc_proxy_invoke( + protocol->proxy, + &wf_impl_client_protocol_on_add_filesystem_finished, + context, + "add_filesystem", + "s", + name); + } + else + { + protocol->callback(protocol->user_data, WF_CLIENT_FILESYSTEM_ADD_FAILED, NULL); + } +} + diff --git a/lib/webfuse/adapter/impl/client_protocol.h b/lib/webfuse/adapter/impl/client_protocol.h new file mode 100644 index 0000000..00c7c26 --- /dev/null +++ b/lib/webfuse/adapter/impl/client_protocol.h @@ -0,0 +1,88 @@ +#ifndef WF_ADAPTER_IMPL_CLIENT_PROTOCOL_H +#define WF_ADAPTER_IMPL_CLIENT_PROTOCOL_H + +#include "webfuse/adapter/client_callback.h" +#include "webfuse/core/slist.h" + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct lws_protocols; +struct lws_context; + +struct wf_impl_filesystem; +struct wf_jsonrpc_proxy; +struct wf_timer_manager; + +typedef void +wf_client_protocol_callback_fn( + void * user_data, + int reason, + void * arg); + +struct wf_client_protocol +{ + bool is_connected; + bool is_shutdown_requested; + struct lws * wsi; + wf_client_protocol_callback_fn * callback; + struct wf_impl_filesystem * filesystem; + void * user_data; + struct wf_timer_manager * timer_manager; + struct wf_jsonrpc_proxy * proxy; + struct wf_slist messages; +}; + +extern void +wf_impl_client_protocol_init( + struct wf_client_protocol * protocol, + wf_client_protocol_callback_fn * callback, + void * user_data); + +extern void +wf_impl_client_protocol_cleanup( + struct wf_client_protocol * protocol); + +extern void +wf_impl_client_protocol_callback( + struct wf_client_protocol * protocol, + int reason, + void * arg); + +extern void +wf_impl_client_protocol_init_lws( + struct wf_client_protocol * protocol, + struct lws_protocols * lws_protocol); + +extern void +wf_impl_client_protocol_connect( + struct wf_client_protocol * protocol, + struct lws_context * conext, + char const * url); + +extern void +wf_impl_client_protocol_disconnect( + struct wf_client_protocol * protocol); + +extern void +wf_impl_client_protocol_authenticate( + struct wf_client_protocol * protocol); + +extern void +wf_impl_client_protocol_add_filesystem( + struct wf_client_protocol * protocol, + char const * local_path, + char const * name); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/lib/webfuse/adapter/impl/client_tlsconfig.c b/lib/webfuse/adapter/impl/client_tlsconfig.c new file mode 100644 index 0000000..561b4b2 --- /dev/null +++ b/lib/webfuse/adapter/impl/client_tlsconfig.c @@ -0,0 +1,56 @@ +#include "webfuse/adapter/impl/client_tlsconfig.h" + +#include +#include + +void +wf_impl_client_tlsconfig_init( + struct wf_client_tlsconfig * config) +{ + config->key_path = NULL; + config->cert_path = NULL; + config->cafile_path = NULL; +} + +void +wf_impl_client_tlsconfig_cleanup( + struct wf_client_tlsconfig * config) +{ + free(config->key_path); + free(config->cert_path); + free(config->cafile_path); +} + +void +wf_impl_client_tlsconfig_set_keypath( + struct wf_client_tlsconfig * config, + char const * key_path) +{ + free(config->key_path); + config->key_path = strdup(key_path); +} + +void +wf_impl_client_tlsconfig_set_certpath( + struct wf_client_tlsconfig * config, + char const * cert_path) +{ + free(config->cert_path); + config->cert_path = strdup(cert_path); +} + +void +wf_impl_client_tlsconfig_set_cafilepath( + struct wf_client_tlsconfig * config, + char const * cafile_path) +{ + free(config->cafile_path); + config->cafile_path = strdup(cafile_path); +} + +bool +wf_impl_client_tlsconfig_isset( + struct wf_client_tlsconfig const * config) +{ + return (NULL != config->cert_path) && (NULL != config->key_path); +} diff --git a/lib/webfuse/adapter/impl/client_tlsconfig.h b/lib/webfuse/adapter/impl/client_tlsconfig.h new file mode 100644 index 0000000..1bbddeb --- /dev/null +++ b/lib/webfuse/adapter/impl/client_tlsconfig.h @@ -0,0 +1,52 @@ +#ifndef WF_ADAPTER_IMPL_CLIENT_TLSCONFIG_H +#define WF_ADAPTER_IMPL_CLIENT_TLSCONFIG_H + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct wf_client_tlsconfig +{ + char * key_path; + char * cert_path; + char * cafile_path; +}; + +extern void +wf_impl_client_tlsconfig_init( + struct wf_client_tlsconfig * config); + +extern void +wf_impl_client_tlsconfig_cleanup( + struct wf_client_tlsconfig * config); + +extern void +wf_impl_client_tlsconfig_set_keypath( + struct wf_client_tlsconfig * config, + char const * key_path); + +extern void +wf_impl_client_tlsconfig_set_certpath( + struct wf_client_tlsconfig * config, + char const * cert_path); + +extern void +wf_impl_client_tlsconfig_set_cafilepath( + struct wf_client_tlsconfig * config, + char const * cafile_path); + +extern bool +wf_impl_client_tlsconfig_isset( + struct wf_client_tlsconfig const * config); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/webfuse/adapter/impl/credentials.c b/lib/webfuse/adapter/impl/credentials.c index 040638a..d4c9461 100644 --- a/lib/webfuse/adapter/impl/credentials.c +++ b/lib/webfuse/adapter/impl/credentials.c @@ -1,6 +1,13 @@ #include "webfuse/adapter/impl/credentials.h" #include +void wf_impl_credentials_init_default( + struct wf_credentials * credentials) +{ + credentials->type = NULL; + credentials->data = json_object(); +} + void wf_impl_credentials_init( struct wf_credentials * credentials, char const * type, @@ -38,3 +45,20 @@ char const * wf_impl_credentials_get( return result; } + +void wf_impl_credentials_set_type( + struct wf_credentials * credentials, + char const * type) +{ + free(credentials->type); + credentials->type = strdup(type); +} + +void wf_impl_credentials_add( + struct wf_credentials * credentials, + char const * key, + char const * value) +{ + json_object_set_new(credentials->data, key, json_string(value)); +} + diff --git a/lib/webfuse/adapter/impl/credentials.h b/lib/webfuse/adapter/impl/credentials.h index a2cd647..59b4c0c 100644 --- a/lib/webfuse/adapter/impl/credentials.h +++ b/lib/webfuse/adapter/impl/credentials.h @@ -19,6 +19,9 @@ extern void wf_impl_credentials_init( char const * type, json_t * data); +extern void wf_impl_credentials_init_default( + struct wf_credentials * credentials); + extern void wf_impl_credentials_cleanup( struct wf_credentials * credentials); @@ -29,6 +32,15 @@ extern char const * wf_impl_credentials_get( struct wf_credentials const * credentials, char const * key); +extern void wf_impl_credentials_set_type( + struct wf_credentials * credentials, + char const * type); + +extern void wf_impl_credentials_add( + struct wf_credentials * credentials, + char const * key, + char const * value); + #ifdef __cplusplus } #endif diff --git a/lib/webfuse/adapter/impl/filesystem.c b/lib/webfuse/adapter/impl/filesystem.c index a033190..453f67f 100644 --- a/lib/webfuse/adapter/impl/filesystem.c +++ b/lib/webfuse/adapter/impl/filesystem.c @@ -9,8 +9,6 @@ #include "webfuse/adapter/impl/session.h" #include "webfuse/adapter/impl/mountpoint.h" -#include "webfuse/core/string.h" - #include #include @@ -52,7 +50,8 @@ static void wf_impl_filesystem_cleanup( static bool wf_impl_filesystem_init( struct wf_impl_filesystem * filesystem, - struct wf_impl_session * session, + struct lws * session_wsi, + struct wf_jsonrpc_proxy * proxy, char const * name, struct wf_mountpoint * mountpoint) { @@ -63,7 +62,7 @@ static bool wf_impl_filesystem_init( filesystem->args.argv = argv; filesystem->args.allocated = 0; - filesystem->user_data.session = session; + filesystem->user_data.proxy = proxy; filesystem->user_data.timeout = 1.0; filesystem->user_data.name = strdup(name); memset(&filesystem->buffer, 0, sizeof(struct fuse_buf)); @@ -85,8 +84,8 @@ static bool wf_impl_filesystem_init( { lws_sock_file_fd_type fd; fd.filefd = fuse_session_fd(filesystem->session); - struct lws_protocols const * protocol = lws_get_protocol(session->wsi); - filesystem->wsi = lws_adopt_descriptor_vhost(lws_get_vhost(session->wsi), LWS_ADOPT_RAW_FILE_DESC, fd, protocol->name, session->wsi); + struct lws_protocols const * protocol = lws_get_protocol(session_wsi); + filesystem->wsi = lws_adopt_descriptor_vhost(lws_get_vhost(session_wsi), LWS_ADOPT_RAW_FILE_DESC, fd, protocol->name, session_wsi); if (NULL == filesystem->wsi) { @@ -100,12 +99,13 @@ static bool wf_impl_filesystem_init( } struct wf_impl_filesystem * wf_impl_filesystem_create( - struct wf_impl_session * session, + struct lws * session_wsi, + struct wf_jsonrpc_proxy * proxy, char const * name, struct wf_mountpoint * mountpoint) { struct wf_impl_filesystem * filesystem = malloc(sizeof(struct wf_impl_filesystem)); - bool success = wf_impl_filesystem_init(filesystem, session, name, mountpoint); + bool success = wf_impl_filesystem_init(filesystem, session_wsi, proxy, name, mountpoint); if (!success) { free(filesystem); diff --git a/lib/webfuse/adapter/impl/filesystem.h b/lib/webfuse/adapter/impl/filesystem.h index 791170a..95825f2 100644 --- a/lib/webfuse/adapter/impl/filesystem.h +++ b/lib/webfuse/adapter/impl/filesystem.h @@ -15,7 +15,7 @@ extern "C" #endif struct wf_mountpoint; -struct wf_impl_session; +struct wf_jsonrpc_proxy; struct lws; struct wf_impl_filesystem @@ -30,7 +30,8 @@ struct wf_impl_filesystem }; extern struct wf_impl_filesystem * wf_impl_filesystem_create( - struct wf_impl_session * session, + struct lws * session_wsi, + struct wf_jsonrpc_proxy * proxy, char const * name, struct wf_mountpoint * mountpoint); diff --git a/lib/webfuse/adapter/impl/operation/context.c b/lib/webfuse/adapter/impl/operation/context.c index a39978f..3e9829d 100644 --- a/lib/webfuse/adapter/impl/operation/context.c +++ b/lib/webfuse/adapter/impl/operation/context.c @@ -6,13 +6,5 @@ struct wf_jsonrpc_proxy * wf_impl_operation_context_get_proxy( struct wf_impl_operation_context * context) { - struct wf_jsonrpc_proxy * proxy = NULL; - - struct wf_impl_session * session = context->session; - if (NULL != session) - { - proxy = session->rpc; - } - - return proxy; + return context->proxy; } diff --git a/lib/webfuse/adapter/impl/operation/context.h b/lib/webfuse/adapter/impl/operation/context.h index 51d886f..1384f72 100644 --- a/lib/webfuse/adapter/impl/operation/context.h +++ b/lib/webfuse/adapter/impl/operation/context.h @@ -7,12 +7,11 @@ extern "C" { #endif -struct wf_impl_session; struct wf_jsonrpc_proxy; struct wf_impl_operation_context { - struct wf_impl_session * session; + struct wf_jsonrpc_proxy * proxy; double timeout; char * name; }; diff --git a/lib/webfuse/adapter/impl/server.c b/lib/webfuse/adapter/impl/server.c index 8f9a01d..441b736 100644 --- a/lib/webfuse/adapter/impl/server.c +++ b/lib/webfuse/adapter/impl/server.c @@ -22,6 +22,7 @@ struct wf_server struct lws_context * context; struct lws_http_mount mount; struct lws_context_creation_info info; + int port; }; static bool wf_impl_server_tls_enabled( @@ -55,6 +56,7 @@ static struct lws_context * wf_impl_server_context_create( server->info.vhost_name = server->config.vhost_name; server->info.ws_ping_pong_interval = 10; server->info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + server->info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; if (NULL == server->config.document_root) { @@ -71,8 +73,11 @@ static struct lws_context * wf_impl_server_context_create( } struct lws_context * const context = lws_create_context(&server->info); - return context; + struct lws_vhost * const vhost = lws_create_vhost(context, &server->info); + server->port = lws_get_vhost_port(vhost); + + return context; } struct wf_server * wf_impl_server_create( @@ -119,3 +124,8 @@ void wf_impl_server_interrupt( lws_cancel_service(server->context); } +extern int wf_impl_server_get_port( + struct wf_server const * server) +{ + return server->port; +} diff --git a/lib/webfuse/adapter/impl/server.h b/lib/webfuse/adapter/impl/server.h index 56dab8d..8216635 100644 --- a/lib/webfuse/adapter/impl/server.h +++ b/lib/webfuse/adapter/impl/server.h @@ -28,6 +28,9 @@ extern void wf_impl_server_service( extern void wf_impl_server_interrupt( struct wf_server * server); +extern int wf_impl_server_get_port( + struct wf_server const * server); + #ifdef __cplusplus } #endif diff --git a/lib/webfuse/adapter/impl/session.c b/lib/webfuse/adapter/impl/session.c index 159f43a..aa950a7 100644 --- a/lib/webfuse/adapter/impl/session.c +++ b/lib/webfuse/adapter/impl/session.c @@ -108,7 +108,7 @@ bool wf_impl_session_add_filesystem( if (result) { - struct wf_impl_filesystem * filesystem = wf_impl_filesystem_create(session, name, mountpoint); + struct wf_impl_filesystem * filesystem = wf_impl_filesystem_create(session->wsi, session->rpc, name, mountpoint); result = (NULL != filesystem); if (result) { diff --git a/lib/webfuse/core/string.c b/lib/webfuse/core/string.c deleted file mode 100644 index 84608cb..0000000 --- a/lib/webfuse/core/string.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "webfuse/core/string.h" - -#include -#include - -char * wf_create_string(char const * format, ...) -{ - char * result = NULL; - - va_list measure_args; - va_start(measure_args, format); - char buffer; - int needed = vsnprintf(&buffer, 1, format, measure_args); - va_end(measure_args); - - if (0 <= needed) - { - result = malloc(needed + 1); - va_list args; - va_start(args, format); - int count = vsnprintf(result, needed + 1, format, args); - va_end(args); - - if ((count < 0) || (needed < count)) - { - free(result); - result = NULL; - } - } - - return result; -} diff --git a/lib/webfuse/core/string.h b/lib/webfuse/core/string.h deleted file mode 100644 index 00a686b..0000000 --- a/lib/webfuse/core/string.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef WF_CORE_STRING_H -#define WF_CORE_STRING_H - -#ifndef __cplusplus -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -extern char * wf_create_string(char const * format, ...); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/webfuse/provider/impl/url.c b/lib/webfuse/core/url.c similarity index 64% rename from lib/webfuse/provider/impl/url.c rename to lib/webfuse/core/url.c index dc6813c..6556362 100644 --- a/lib/webfuse/provider/impl/url.c +++ b/lib/webfuse/core/url.c @@ -1,9 +1,9 @@ -#include "webfuse/provider/impl/url.h" +#include "webfuse/core/url.h" #include #include -struct wfp_impl_url_protocol +struct wf_url_protocol { char const * name; size_t name_length; @@ -11,11 +11,11 @@ struct wfp_impl_url_protocol bool use_tls; }; -static bool wfp_impl_url_readprotocol( - struct wfp_impl_url * url, +static bool wf_url_readprotocol( + struct wf_url * url, char const * * data) { - static struct wfp_impl_url_protocol const known_protocols[] = + static struct wf_url_protocol const known_protocols[] = { {"ws://", 5, 80, false}, {"wss://", 6, 443, true} @@ -25,7 +25,7 @@ static bool wfp_impl_url_readprotocol( bool found = false; for(size_t i = 0; (!found) && (i < count); i++) { - struct wfp_impl_url_protocol const * protocol = &known_protocols[i]; + struct wf_url_protocol const * protocol = &known_protocols[i]; if (0 == strncmp(*data, protocol->name, protocol->name_length)) { url->port = protocol->default_port; @@ -38,8 +38,8 @@ static bool wfp_impl_url_readprotocol( return found; } -static bool wfp_impl_url_readhost( - struct wfp_impl_url * url, +static bool wf_url_readhost( + struct wf_url * url, char const * * data) { char * end = strpbrk(*data, ":/"); @@ -55,8 +55,8 @@ static bool wfp_impl_url_readhost( return result; } -static bool wfp_impl_url_readport( - struct wfp_impl_url * url, +static bool wf_url_readport( + struct wf_url * url, char const * * data) { bool result; @@ -81,8 +81,8 @@ static bool wfp_impl_url_readport( return result; } -static bool wfp_impl_url_readpath( - struct wfp_impl_url * url, +static bool wf_url_readpath( + struct wf_url * url, char const * * data) { bool const result = ('/' == **data); @@ -93,33 +93,33 @@ static bool wfp_impl_url_readpath( } -bool wfp_impl_url_init( - struct wfp_impl_url * url, +bool wf_url_init( + struct wf_url * url, char const * value) { - memset(url, 0, sizeof(struct wfp_impl_url)); + memset(url, 0, sizeof(struct wf_url)); char const * data = value; bool const result = - wfp_impl_url_readprotocol(url, &data) && - wfp_impl_url_readhost(url, &data) && - wfp_impl_url_readport(url, &data) && - wfp_impl_url_readpath(url, &data) + wf_url_readprotocol(url, &data) && + wf_url_readhost(url, &data) && + wf_url_readport(url, &data) && + wf_url_readpath(url, &data) ; if (!result) { - wfp_impl_url_cleanup(url); + wf_url_cleanup(url); } return result; } -void wfp_impl_url_cleanup( - struct wfp_impl_url * url) +void wf_url_cleanup( + struct wf_url * url) { free(url->host); free(url->path); - memset(url, 0, sizeof(struct wfp_impl_url)); + memset(url, 0, sizeof(struct wf_url)); } diff --git a/lib/webfuse/provider/impl/url.h b/lib/webfuse/core/url.h similarity index 51% rename from lib/webfuse/provider/impl/url.h rename to lib/webfuse/core/url.h index 7755e5c..4f75d8d 100644 --- a/lib/webfuse/provider/impl/url.h +++ b/lib/webfuse/core/url.h @@ -1,5 +1,5 @@ -#ifndef WF_PROVIDER_IMPL_URL_H -#define WF_PROVIDER_IMPL_URL_H +#ifndef WF_URL_H +#define WF_URL_H #ifndef __cplusplus #include @@ -9,7 +9,7 @@ extern "C" { #endif -struct wfp_impl_url +struct wf_url { char * host; int port; @@ -17,12 +17,12 @@ struct wfp_impl_url bool use_tls; }; -extern bool wfp_impl_url_init( - struct wfp_impl_url * url, +extern bool wf_url_init( + struct wf_url * url, char const * value); -extern void wfp_impl_url_cleanup( - struct wfp_impl_url * url); +extern void wf_url_cleanup( + struct wf_url * url); #ifdef __cplusplus diff --git a/lib/webfuse/provider/impl/client.c b/lib/webfuse/provider/impl/client.c index e7c6e81..2c584e9 100644 --- a/lib/webfuse/provider/impl/client.c +++ b/lib/webfuse/provider/impl/client.c @@ -82,12 +82,6 @@ void wfp_impl_client_disconnect( wfp_impl_client_protocol_disconnect(&client->protocol); } -bool wfp_impl_client_is_connected( - struct wfp_client * client) -{ - return client->protocol.is_connected; -} - void wfp_impl_client_service( struct wfp_client * client) { diff --git a/lib/webfuse/provider/impl/client.h b/lib/webfuse/provider/impl/client.h index 46e41f9..0dfc64e 100644 --- a/lib/webfuse/provider/impl/client.h +++ b/lib/webfuse/provider/impl/client.h @@ -34,9 +34,6 @@ extern void wfp_impl_client_disconnect( extern void wfp_impl_client_dispose( struct wfp_client * client); -extern bool wfp_impl_client_is_connected( - struct wfp_client * client); - extern void wfp_impl_client_service( struct wfp_client * client); diff --git a/lib/webfuse/provider/impl/client_protocol.c b/lib/webfuse/provider/impl/client_protocol.c index db78137..7ce8c2e 100644 --- a/lib/webfuse/provider/impl/client_protocol.c +++ b/lib/webfuse/provider/impl/client_protocol.c @@ -13,7 +13,7 @@ #include "webfuse/core/message.h" #include "webfuse/core/message_queue.h" #include "webfuse/core/container_of.h" -#include "webfuse/provider/impl/url.h" +#include "webfuse/core/url.h" #include "webfuse/core/protocol_names.h" #include "webfuse/core/timer/manager.h" @@ -297,8 +297,8 @@ void wfp_impl_client_protocol_connect( struct lws_context * context, char const * url) { - struct wfp_impl_url url_data; - bool const success = wfp_impl_url_init(&url_data, url); + struct wf_url url_data; + bool const success = wf_url_init(&url_data, url); if (success) { struct lws_client_connect_info info; @@ -316,7 +316,7 @@ void wfp_impl_client_protocol_connect( lws_client_connect_via_info(&info); - wfp_impl_url_cleanup(&url_data); + wf_url_cleanup(&url_data); } else { diff --git a/meson.build b/meson.build index e08f828..4842fea 100644 --- a/meson.build +++ b/meson.build @@ -25,10 +25,10 @@ webfuse_core = static_library('webfuse_core', 'lib/webfuse/core/message.c', 'lib/webfuse/core/message_queue.c', 'lib/webfuse/core/status.c', - 'lib/webfuse/core/string.c', 'lib/webfuse/core/base64.c', 'lib/webfuse/core/lws_log.c', 'lib/webfuse/core/json_util.c', + 'lib/webfuse/core/url.c', 'lib/webfuse/core/timer/manager.c', 'lib/webfuse/core/timer/timepoint.c', 'lib/webfuse/core/timer/timer.c', @@ -56,7 +56,6 @@ if not without_provider webfuse_provider_static = static_library('webfuse_provider', 'lib/webfuse/provider/api.c', - 'lib/webfuse/provider/impl/url.c', 'lib/webfuse/provider/impl/client.c', 'lib/webfuse/provider/impl/client_config.c', 'lib/webfuse/provider/impl/client_protocol.c', @@ -132,6 +131,9 @@ webfuse_adapter_static = static_library('webfuse_adapter', 'lib/webfuse/adapter/impl/operation/open.c', 'lib/webfuse/adapter/impl/operation/close.c', 'lib/webfuse/adapter/impl/operation/read.c', + 'lib/webfuse/adapter/impl/client.c', + 'lib/webfuse/adapter/impl/client_protocol.c', + 'lib/webfuse/adapter/impl/client_tlsconfig.c', c_args: ['-fvisibility=hidden'], include_directories: private_inc_dir, dependencies: [webfuse_core_dep, libfuse_dep]) @@ -207,6 +209,9 @@ alltests = executable('alltests', 'test/webfuse/utils/path.c', 'test/webfuse/utils/static_filesystem.c', 'test/webfuse/utils/ws_server.cc', + 'test/webfuse/utils/ws_server2.cc', + 'test/webfuse/utils/adapter_client.cc', + 'test/webfuse/utils/jansson_test_environment.cc', 'test/webfuse/mocks/fake_invokation_context.cc', 'test/webfuse/mocks/mock_authenticator.cc', 'test/webfuse/mocks/mock_request.cc', @@ -215,14 +220,15 @@ alltests = executable('alltests', 'test/webfuse/mocks/mock_fuse.cc', 'test/webfuse/mocks/mock_operation_context.cc', 'test/webfuse/mocks/mock_jsonrpc_proxy.cc', + 'test/webfuse/mocks/mock_adapter_client_callback.cc', 'test/webfuse//tests/core/test_util.cc', 'test/webfuse/tests/core/test_container_of.cc', - 'test/webfuse/tests/core/test_string.cc', 'test/webfuse/tests/core/test_slist.cc', 'test/webfuse/tests/core/test_base64.cc', 'test/webfuse/tests/core/test_status.cc', 'test/webfuse/tests/core/test_message.cc', 'test/webfuse/tests/core/test_message_queue.cc', + 'test/webfuse/tests/core/test_url.cc', 'test/webfuse/tests/adapter/test_server.cc', 'test/webfuse/tests/adapter/test_server_config.cc', 'test/webfuse/tests/adapter/test_credentials.cc', @@ -237,8 +243,8 @@ alltests = executable('alltests', 'test/webfuse/tests/adapter/operation/test_readdir.cc', 'test/webfuse/tests/adapter/operation/test_getattr.cc', 'test/webfuse/tests/adapter/operation/test_lookup.cc', - 'test/webfuse/tests/provider/test_url.cc', 'test/webfuse/tests/provider/test_client_protocol.cc', + 'test/webfuse/tests/provider/test_dirbuffer.cc', 'test/webfuse/tests/provider/operation/test_close.cc', 'test/webfuse/tests/provider/operation/test_getattr.cc', 'test/webfuse/tests/provider/operation/test_lookup.cc', @@ -250,6 +256,8 @@ alltests = executable('alltests', 'test/webfuse/tests/integration/file.cc', 'test/webfuse/tests/integration/server.cc', 'test/webfuse/tests/integration/provider.cc', + 'test/webfuse/tests/adapter/test_client.cc', + 'test/webfuse/tests/adapter/test_client_tlsconfig.cc', link_args: [ '-Wl,--wrap=wf_timer_manager_create', '-Wl,--wrap=wf_timer_manager_dispose', diff --git a/test/webfuse/mocks/lookup_matcher.hpp b/test/webfuse/mocks/lookup_matcher.hpp new file mode 100644 index 0000000..e9c5a55 --- /dev/null +++ b/test/webfuse/mocks/lookup_matcher.hpp @@ -0,0 +1,50 @@ +#ifndef WF_LOOKUP_MATCHER_HPP +#define WF_LOOKUP_MATCHER_HPP + +#include +#include +#include + +namespace webfuse_test +{ + +MATCHER_P2(Lookup, parent, name, "") +{ + if (!json_is_array(arg)) + { + *result_listener << "json array expected"; + return false; + } + + json_t * parent_ = json_array_get(arg, 1); + if (!json_is_integer(parent_)) + { + *result_listener << "parent is expected to be an integer"; + return false; + } + if (parent != json_integer_value(parent_)) + { + *result_listener << "parent mismatch: expected " << parent + << " but was " << json_integer_value(parent_); + return false; + } + + json_t * name_ = json_array_get(arg, 2); + if (!json_is_string(name_)) + { + *result_listener << "name is expected to be a string"; + return false; + } + if (0 != strcmp(name, json_string_value(name_))) + { + *result_listener << "name mismatch: expected \"" << name + << "\" but was \"" << json_string_value(name_) << "\""; + return false; + } + + return true; +} + +} + +#endif diff --git a/test/webfuse/mocks/mock_adapter_client_callback.cc b/test/webfuse/mocks/mock_adapter_client_callback.cc new file mode 100644 index 0000000..4407497 --- /dev/null +++ b/test/webfuse/mocks/mock_adapter_client_callback.cc @@ -0,0 +1,44 @@ +#include "webfuse/mocks/mock_adapter_client_callback.hpp" +#include "webfuse/adapter/client.h" + +extern "C" +{ + +static void +webfuse_test_MockAdapterClientCallback_callback( + wf_client * client, + int reason, + void * args) +{ + void * user_data = wf_client_get_userdata(client); + auto * callback = reinterpret_cast(user_data); + + callback->Invoke(client, reason, args); +} + +} + +namespace webfuse_test +{ + +MockAdapterClientCallback::MockAdapterClientCallback() +{ + +} + +MockAdapterClientCallback::~MockAdapterClientCallback() +{ + +} + +void * MockAdapterClientCallback::GetUserData() +{ + return reinterpret_cast(this); +} + +wf_client_callback_fn * MockAdapterClientCallback::GetCallbackFn() +{ + return &webfuse_test_MockAdapterClientCallback_callback; +} + +} \ No newline at end of file diff --git a/test/webfuse/mocks/mock_adapter_client_callback.hpp b/test/webfuse/mocks/mock_adapter_client_callback.hpp new file mode 100644 index 0000000..cd66c7e --- /dev/null +++ b/test/webfuse/mocks/mock_adapter_client_callback.hpp @@ -0,0 +1,22 @@ +#ifndef WF_MOCK_ADAPTER_CLIENT_CALLBACK_HPP +#define WF_MOCK_ADAPTER_CLIENT_CALLBACK_HPP + +#include +#include "webfuse/adapter/client_callback.h" + +namespace webfuse_test +{ + +class MockAdapterClientCallback +{ +public: + MockAdapterClientCallback(); + virtual ~MockAdapterClientCallback(); + MOCK_METHOD3(Invoke, void (wf_client *, int, void *)); + void * GetUserData(); + wf_client_callback_fn * GetCallbackFn(); +}; + +} + +#endif diff --git a/test/webfuse/mocks/mock_authenticator.cc b/test/webfuse/mocks/mock_authenticator.cc index 35648ed..4aee1bb 100644 --- a/test/webfuse/mocks/mock_authenticator.cc +++ b/test/webfuse/mocks/mock_authenticator.cc @@ -23,17 +23,17 @@ void set_authenticator(size_t i, Authenticator * authenticator) g_authenticators[i] = authenticator; } -bool authenticate(struct wf_credentials * creds, void * user_data) +bool authenticate(struct wf_credentials const * creds, void * user_data) { return g_authenticators[0]->authenticate(creds, user_data); } -bool authenticate_1(struct wf_credentials * creds, void * user_data) +bool authenticate_1(struct wf_credentials const * creds, void * user_data) { return g_authenticators[1]->authenticate(creds, user_data); } -bool authenticate_2(struct wf_credentials * creds, void * user_data) +bool authenticate_2(struct wf_credentials const * creds, void * user_data) { return g_authenticators[2]->authenticate(creds, user_data); } diff --git a/test/webfuse/mocks/mock_authenticator.hpp b/test/webfuse/mocks/mock_authenticator.hpp index bb38be3..ac7f2bf 100644 --- a/test/webfuse/mocks/mock_authenticator.hpp +++ b/test/webfuse/mocks/mock_authenticator.hpp @@ -12,22 +12,22 @@ class Authenticator public: virtual ~Authenticator() { } virtual bool authenticate( - struct wf_credentials * credentials, + struct wf_credentials const * credentials, void * user_data) = 0; }; class MockAuthenticator: public Authenticator { public: - MOCK_METHOD2(authenticate, bool (struct wf_credentials * credentials, void * user_data)); + MOCK_METHOD2(authenticate, bool (struct wf_credentials const * credentials, void * user_data)); }; void set_authenticator(Authenticator * authenticator); void set_authenticator(size_t index, Authenticator * authenticator); -bool authenticate(struct wf_credentials * creds, void * user_data); -bool authenticate_1(struct wf_credentials * creds, void * user_data); -bool authenticate_2(struct wf_credentials * creds, void * user_data); +bool authenticate(struct wf_credentials const * creds, void * user_data); +bool authenticate_1(struct wf_credentials const * creds, void * user_data); +bool authenticate_2(struct wf_credentials const * creds, void * user_data); } diff --git a/test/webfuse/mocks/mock_invokation_handler.hpp b/test/webfuse/mocks/mock_invokation_handler.hpp new file mode 100644 index 0000000..48d3b8b --- /dev/null +++ b/test/webfuse/mocks/mock_invokation_handler.hpp @@ -0,0 +1,18 @@ +#ifndef WF_MOCK_INVOKATION_HANDLER_HPP +#define WF_MOCK_INVOKATION_HANDLER_HPP + +#include "webfuse/utils/ws_server2.hpp" +#include + +namespace webfuse_test +{ + +class MockInvokationHander: public IIvokationHandler +{ +public: + MOCK_METHOD2(Invoke, std::string(char const * method, json_t * params)); +}; + +} + +#endif diff --git a/test/webfuse/tests/adapter/operation/test_context.cc b/test/webfuse/tests/adapter/operation/test_context.cc index 77aa287..75e619e 100644 --- a/test/webfuse/tests/adapter/operation/test_context.cc +++ b/test/webfuse/tests/adapter/operation/test_context.cc @@ -5,10 +5,8 @@ TEST(wf_impl_operation_context, get_proxy) { wf_jsonrpc_proxy * proxy = reinterpret_cast(42); - wf_impl_session session; - session.rpc = proxy; wf_impl_operation_context context; - context.session = &session; + context.proxy = proxy; ASSERT_EQ(proxy, wf_impl_operation_context_get_proxy(&context)); } @@ -16,7 +14,7 @@ TEST(wf_impl_operation_context, get_proxy) TEST(wf_impl_operation_context, get_proxy_fail_no_session) { wf_impl_operation_context context; - context.session = nullptr; + context.proxy = nullptr; ASSERT_EQ(nullptr, wf_impl_operation_context_get_proxy(&context)); diff --git a/test/webfuse/tests/adapter/test_client.cc b/test/webfuse/tests/adapter/test_client.cc new file mode 100644 index 0000000..78cf7b2 --- /dev/null +++ b/test/webfuse/tests/adapter/test_client.cc @@ -0,0 +1,579 @@ +#include +#include + +#include "webfuse/utils/adapter_client.hpp" +#include "webfuse/adapter/client_tlsconfig.h" +#include "webfuse/adapter/credentials.h" +#include "webfuse/core/protocol_names.h" +#include "webfuse/utils/ws_server2.hpp" +#include "webfuse/mocks/mock_adapter_client_callback.hpp" +#include "webfuse/mocks/mock_invokation_handler.hpp" +#include "webfuse/utils/timeout_watcher.hpp" +#include "webfuse/tests/integration/file.hpp" +#include "webfuse/mocks/lookup_matcher.hpp" + +using webfuse_test::AdapterClient; +using webfuse_test::WsServer2; +using webfuse_test::MockInvokationHander; +using webfuse_test::MockAdapterClientCallback; +using webfuse_test::TimeoutWatcher; +using webfuse_test::File; +using webfuse_test::Lookup; +using testing::_; +using testing::Invoke; +using testing::AnyNumber; +using testing::Return; +using testing::Throw; +using testing::StrEq; + +#define TIMEOUT (std::chrono::milliseconds(30 * 1000)) + +namespace +{ + +void GetCredentials(wf_client *, int, void * arg) +{ + auto * creds = reinterpret_cast(arg); + wf_credentials_set_type(creds, "username"); + wf_credentials_add(creds, "username", "Bob"); + wf_credentials_add(creds, "password", "secret"); +} + +} + +TEST(AdapterClient, CreateAndDispose) +{ + MockAdapterClientCallback callback; + + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_INIT, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CREATED, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_GET_TLS_CONFIG, _)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CLEANUP, nullptr)).Times(1); + + wf_client * client = wf_client_create( + callback.GetCallbackFn(), callback.GetUserData()); + + wf_client_dispose(client); +} + +TEST(AdapterClient, Connect) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(_,_)).Times(0); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_INIT, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CREATED, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_GET_TLS_CONFIG, _)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CLEANUP, nullptr)).Times(1); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, IgnoreNonJsonMessage) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(_,_)).Times(0); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_INIT, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CREATED, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_GET_TLS_CONFIG, _)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CLEANUP, nullptr)).Times(1); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + server.SendMessage("brummni"); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + + +TEST(AdapterClient, IgnoreInvalidJsonMessage) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(_,_)).Times(0); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_INIT, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CREATED, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_GET_TLS_CONFIG, _)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CLEANUP, nullptr)).Times(1); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + json_t * invalid_request = json_object(); + server.SendMessage(invalid_request); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, ConnectWithTls) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER, 0, true); + EXPECT_CALL(handler, Invoke(_,_)).Times(0); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_INIT, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CREATED, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_GET_TLS_CONFIG, _)).Times(1) + .WillOnce(Invoke([](wf_client *, int, void * arg) { + auto * tls = reinterpret_cast(arg); + wf_client_tlsconfig_set_keypath (tls, "client-key.pem"); + wf_client_tlsconfig_set_certpath(tls, "client-cert.pem"); + wf_client_tlsconfig_set_cafilepath(tls, "server-cert.pem"); + })); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CLEANUP, nullptr)).Times(1); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, FailedToConnectInvalidPort) +{ + TimeoutWatcher watcher(TIMEOUT); + + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_INIT, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CREATED, nullptr)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_GET_TLS_CONFIG, _)).Times(1); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CLEANUP, nullptr)).Times(1); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), "ws://localhost:4/"); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + + +TEST(AdapterClient, Authenticate) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("authenticate"),_)).Times(1) + .WillOnce(Return("{}")); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS, _)).Times(1) + .WillOnce(Invoke(GetCredentials)); + + bool authenticated = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_AUTHENTICATED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { authenticated = true; })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.Authenticate(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return authenticated; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, AuthenticateFailedWithoutConnect) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS, _)).Times(1) + .WillOnce(Invoke(GetCredentials)); + bool called = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_AUTHENTICATION_FAILED, nullptr)).Times(1) + .WillOnce(Invoke([&called] (wf_client *, int, void *) mutable { + called = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), ""); + + client.Authenticate(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return called; })); +} + +TEST(AdapterClient, AuthenticationFailed) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("authenticate"),_)).Times(1) + .WillOnce(Throw(std::runtime_error("authentication failed"))); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_AUTHENTICATE_GET_CREDENTIALS, _)).Times(1) + .WillOnce(Invoke(GetCredentials)); + + bool called = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_AUTHENTICATION_FAILED, nullptr)).Times(1) + .WillOnce(Invoke([&called] (wf_client *, int, void *) mutable { + called = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.Authenticate(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return called; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, AddFileSystem) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("add_filesystem"),_)).Times(1) + .WillOnce(Return("{\"id\": \"test\"}")); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()) + .WillRepeatedly(Throw(std::runtime_error("unknown"))); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + bool called = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADDED, nullptr)).Times(1) + .WillOnce(Invoke([&called] (wf_client *, int, void *) mutable { + called = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return called; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, FailToAddFileSystemTwice) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("add_filesystem"),_)).Times(1) + .WillOnce(Return("{\"id\": \"test\"}")); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()) + .WillRepeatedly(Throw(std::runtime_error("unknown"))); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + bool filesystem_added = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADDED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { + filesystem_added = true; + })); + + bool filesystem_add_failed = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADD_FAILED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { + filesystem_add_failed = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return filesystem_added; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return filesystem_add_failed; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, FailToAddFileSystemMissingId) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("add_filesystem"),_)).Times(1) + .WillOnce(Return("{}")); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()) + .WillRepeatedly(Throw(std::runtime_error("unknown"))); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + bool filesystem_add_failed = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADD_FAILED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { + filesystem_add_failed = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return filesystem_add_failed; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, FailToAddFileSystemIdNotString) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("add_filesystem"),_)).Times(1) + .WillOnce(Return("{\"id\": 42}")); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()) + .WillRepeatedly(Throw(std::runtime_error("unknown"))); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + bool filesystem_add_failed = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADD_FAILED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { + filesystem_add_failed = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return filesystem_add_failed; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + + +TEST(AdapterClient, AddFileSystemFailed) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("add_filesystem"),_)).Times(1) + .WillOnce(Throw(std::runtime_error("failed"))); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + bool called = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADD_FAILED, nullptr)).Times(1) + .WillOnce(Invoke([&called] (wf_client *, int, void *) mutable { + called = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return called; })); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} + +TEST(AdapterClient, LookupFile) +{ + TimeoutWatcher watcher(TIMEOUT); + + MockInvokationHander handler; + WsServer2 server(handler, WF_PROTOCOL_NAME_PROVIDER_SERVER); + EXPECT_CALL(handler, Invoke(StrEq("add_filesystem"),_)).Times(1) + .WillOnce(Return("{\"id\": \"test\"}")); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()) + .WillRepeatedly(Throw(std::runtime_error("unknown"))); + EXPECT_CALL(handler, Invoke(StrEq("lookup"), Lookup(1, "Hello.txt"))).Times(AnyNumber()) + .WillRepeatedly(Return( + "{" + "\"inode\": 2," + "\"mode\": 420," //0644 + "\"type\": \"file\"," + "\"size\": 42," + "\"atime\": 0," + "\"mtime\": 0," + "\"ctime\": 0" + "}" + )); + + MockAdapterClientCallback callback; + EXPECT_CALL(callback, Invoke(_, _, _)).Times(AnyNumber()); + + bool connected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_CONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { connected = true; })); + + bool disconnected = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_DISCONNECTED, nullptr)).Times(1) + .WillOnce(Invoke([&] (wf_client *, int, void *) mutable { disconnected = true; })); + + bool called = false; + EXPECT_CALL(callback, Invoke(_, WF_CLIENT_FILESYSTEM_ADDED, nullptr)).Times(1) + .WillOnce(Invoke([&called] (wf_client *, int, void *) mutable { + called = true; + })); + + AdapterClient client(callback.GetCallbackFn(), callback.GetUserData(), server.GetUrl()); + + client.Connect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return connected; })); + + client.AddFileSystem(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return called; })); + + std::string file_name = client.GetDir() + "/Hello.txt"; + File file(file_name); + ASSERT_TRUE(file.isFile()); + + client.Disconnect(); + ASSERT_TRUE(watcher.waitUntil([&]() mutable { return disconnected; })); +} diff --git a/test/webfuse/tests/adapter/test_client_tlsconfig.cc b/test/webfuse/tests/adapter/test_client_tlsconfig.cc new file mode 100644 index 0000000..788b906 --- /dev/null +++ b/test/webfuse/tests/adapter/test_client_tlsconfig.cc @@ -0,0 +1,69 @@ +#include +#include "webfuse/adapter/client_tlsconfig.h" +#include "webfuse/adapter/impl/client_tlsconfig.h" + +TEST(ClientTlsConfig, InitAndCleanup) +{ + wf_client_tlsconfig config; + + wf_impl_client_tlsconfig_init(&config); + wf_impl_client_tlsconfig_cleanup(&config); +} + +TEST(ClientTlsConfig, SetKeyPath) +{ + wf_client_tlsconfig config; + wf_impl_client_tlsconfig_init(&config); + + wf_client_tlsconfig_set_keypath(&config, "/path/to/key.pem"); + ASSERT_STREQ("/path/to/key.pem", config.key_path); + + wf_impl_client_tlsconfig_cleanup(&config); +} + +TEST(ClientTlsConfig, SetCertPath) +{ + wf_client_tlsconfig config; + wf_impl_client_tlsconfig_init(&config); + + wf_client_tlsconfig_set_certpath(&config, "/path/to/cert.pem"); + ASSERT_STREQ("/path/to/cert.pem", config.cert_path); + + wf_impl_client_tlsconfig_cleanup(&config); +} + +TEST(ClientTlsConfig, SetCafilePath) +{ + wf_client_tlsconfig config; + wf_impl_client_tlsconfig_init(&config); + + wf_client_tlsconfig_set_cafilepath(&config, "/path/to/cafile.pem"); + ASSERT_STREQ("/path/to/cafile.pem", config.cafile_path); + + wf_impl_client_tlsconfig_cleanup(&config); +} + +TEST(ClientTslConfig, IsSet) +{ + wf_client_tlsconfig config; + + wf_impl_client_tlsconfig_init(&config); + ASSERT_FALSE(wf_impl_client_tlsconfig_isset(&config)); + wf_impl_client_tlsconfig_cleanup(&config); + + wf_impl_client_tlsconfig_init(&config); + wf_client_tlsconfig_set_keypath(&config, "/path/to/key.pem"); + ASSERT_FALSE(wf_impl_client_tlsconfig_isset(&config)); + wf_impl_client_tlsconfig_cleanup(&config); + + wf_impl_client_tlsconfig_init(&config); + wf_client_tlsconfig_set_certpath(&config, "/path/to/cert.pem"); + ASSERT_FALSE(wf_impl_client_tlsconfig_isset(&config)); + wf_impl_client_tlsconfig_cleanup(&config); + + wf_impl_client_tlsconfig_init(&config); + wf_client_tlsconfig_set_keypath(&config, "/path/to/key.pem"); + wf_client_tlsconfig_set_certpath(&config, "/path/to/cert.pem"); + ASSERT_TRUE(wf_impl_client_tlsconfig_isset(&config)); + wf_impl_client_tlsconfig_cleanup(&config); +} \ No newline at end of file diff --git a/test/webfuse/tests/adapter/test_credentials.cc b/test/webfuse/tests/adapter/test_credentials.cc index 88e2552..2e11a79 100644 --- a/test/webfuse/tests/adapter/test_credentials.cc +++ b/test/webfuse/tests/adapter/test_credentials.cc @@ -83,3 +83,31 @@ TEST(Credentials, FailedToGetWrongElementDataType) wf_impl_credentials_cleanup(&creds); json_decref(data); } + +TEST(Credentials, SetType) +{ + struct wf_credentials creds; + wf_impl_credentials_init_default(&creds); + + wf_credentials_set_type(&creds, "username"); + ASSERT_STREQ("username", wf_credentials_type(&creds)); + + wf_impl_credentials_cleanup(&creds); +} + +TEST(Credentials, Add) +{ + struct wf_credentials creds; + wf_impl_credentials_init_default(&creds); + + wf_credentials_add(&creds, "a.value", "a"); + ASSERT_STREQ("a", wf_credentials_get(&creds, "a.value")); + + wf_credentials_add(&creds, "b.value", "b"); + ASSERT_STREQ("b", wf_credentials_get(&creds, "b.value")); + + wf_credentials_add(&creds, "a.value", "A"); + ASSERT_STREQ("A", wf_credentials_get(&creds, "a.value")); + + wf_impl_credentials_cleanup(&creds); +} diff --git a/test/webfuse/tests/adapter/test_server_config.cc b/test/webfuse/tests/adapter/test_server_config.cc index b9687be..ceec97d 100644 --- a/test/webfuse/tests/adapter/test_server_config.cc +++ b/test/webfuse/tests/adapter/test_server_config.cc @@ -20,7 +20,7 @@ wf_mountpoint * create_mountpoint( } bool authenticate( - wf_credentials * credentials, + wf_credentials const * credentials, void * user_data) { (void) credentials; diff --git a/test/webfuse/tests/core/test_string.cc b/test/webfuse/tests/core/test_string.cc deleted file mode 100644 index 17e501a..0000000 --- a/test/webfuse/tests/core/test_string.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -#include "webfuse/core/string.h" - -TEST(wf_string_create, Default) -{ - char * value = wf_create_string("test %s/%d", "hello", 42); - ASSERT_STREQ("test hello/42", value); - free(value); -} - -TEST(wf_string_create, EmptyString) -{ - char * value = wf_create_string(""); - ASSERT_STREQ("", value); - free(value); -} diff --git a/test/webfuse/tests/provider/test_url.cc b/test/webfuse/tests/core/test_url.cc similarity index 56% rename from test/webfuse/tests/provider/test_url.cc rename to test/webfuse/tests/core/test_url.cc index 7f6a5e7..ba5ba76 100644 --- a/test/webfuse/tests/provider/test_url.cc +++ b/test/webfuse/tests/core/test_url.cc @@ -1,69 +1,69 @@ #include -#include "webfuse/provider/impl/url.h" +#include "webfuse/core/url.h" TEST(url, ParseWs) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "ws://localhost/"); + struct wf_url url; + bool result = wf_url_init(&url, "ws://localhost/"); ASSERT_TRUE(result); ASSERT_EQ(80, url.port); ASSERT_FALSE(url.use_tls); ASSERT_STREQ("localhost", url.host); ASSERT_STREQ("/", url.path); - wfp_impl_url_cleanup(&url); + wf_url_cleanup(&url); } TEST(url, ParswWss) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "wss://localhost/"); + struct wf_url url; + bool result = wf_url_init(&url, "wss://localhost/"); ASSERT_TRUE(result); ASSERT_EQ(443, url.port); ASSERT_TRUE(url.use_tls); ASSERT_STREQ("localhost", url.host); ASSERT_STREQ("/", url.path); - wfp_impl_url_cleanup(&url); + wf_url_cleanup(&url); } TEST(url, ParseIPAdress) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "ws://127.0.0.1/"); + struct wf_url url; + bool result = wf_url_init(&url, "ws://127.0.0.1/"); ASSERT_TRUE(result); ASSERT_EQ(80, url.port); ASSERT_STREQ("127.0.0.1", url.host); ASSERT_STREQ("/", url.path); - wfp_impl_url_cleanup(&url); + wf_url_cleanup(&url); } TEST(url, ParsePort) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "ws://localhost:54321/"); + struct wf_url url; + bool result = wf_url_init(&url, "ws://localhost:54321/"); ASSERT_TRUE(result); ASSERT_EQ(54321, url.port); - wfp_impl_url_cleanup(&url); + wf_url_cleanup(&url); } TEST(url, ParseNonEmptyPath) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "ws://localhost/some_path?query"); + struct wf_url url; + bool result = wf_url_init(&url, "ws://localhost/some_path?query"); ASSERT_TRUE(result); ASSERT_STREQ("/some_path?query", url.path); - wfp_impl_url_cleanup(&url); + wf_url_cleanup(&url); } TEST(url, FailToParseUnknownProtocol) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "unknown://localhost/"); + struct wf_url url; + bool result = wf_url_init(&url, "unknown://localhost/"); ASSERT_FALSE(result); ASSERT_EQ(0, url.port); ASSERT_EQ(nullptr, url.path); @@ -72,8 +72,8 @@ TEST(url, FailToParseUnknownProtocol) TEST(url, FailToParseMissingProtocol) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "unknown"); + struct wf_url url; + bool result = wf_url_init(&url, "unknown"); ASSERT_FALSE(result); ASSERT_EQ(0, url.port); ASSERT_EQ(nullptr, url.path); @@ -82,8 +82,8 @@ TEST(url, FailToParseMissingProtocol) TEST(url, FailToParseMissingPath) { - struct wfp_impl_url url; - bool result = wfp_impl_url_init(&url, "ws://localhost"); + struct wf_url url; + bool result = wf_url_init(&url, "ws://localhost"); ASSERT_FALSE(result); ASSERT_EQ(0, url.port); ASSERT_EQ(nullptr, url.path); diff --git a/test/webfuse/tests/integration/server.cc b/test/webfuse/tests/integration/server.cc index 50badcc..f175210 100644 --- a/test/webfuse/tests/integration/server.cc +++ b/test/webfuse/tests/integration/server.cc @@ -1,6 +1,7 @@ #include "webfuse/tests/integration/server.hpp" #include #include +#include #include #include #include @@ -59,7 +60,7 @@ public: config = wf_server_config_create(); - wf_server_config_set_port(config, 8080); + wf_server_config_set_port(config, 0); wf_server_config_set_mountpoint_factory(config, &webfuse_test_server_create_mountpoint, reinterpret_cast(base_dir)); @@ -92,6 +93,14 @@ public: return is_shutdown_requested; } + std::string GetUrl(void) const + { + int const port = wf_server_get_port(server); + std::ostringstream stream; + stream << "wss://localhost:" << port << "/"; + return stream.str(); + } + private: void RequestShutdown() { @@ -136,5 +145,10 @@ char const * Server::GetBaseDir(void) const return d->base_dir; } +std::string Server::GetUrl(void) const +{ + return d->GetUrl(); +} + } \ No newline at end of file diff --git a/test/webfuse/tests/integration/server.hpp b/test/webfuse/tests/integration/server.hpp index 64f949d..de7864c 100644 --- a/test/webfuse/tests/integration/server.hpp +++ b/test/webfuse/tests/integration/server.hpp @@ -1,6 +1,8 @@ #ifndef WF_TEST_INTEGRATION_SERVER_HPP #define WF_TEST_INTEGRATION_SERVER_HPP +#include + namespace webfuse_test { @@ -12,6 +14,7 @@ public: void Start(void); void Stop(void); char const * GetBaseDir(void) const; + std::string GetUrl(void) const; private: class Private; Private * d; diff --git a/test/webfuse/tests/integration/test_integration.cc b/test/webfuse/tests/integration/test_integration.cc index e7e8215..ae9033c 100644 --- a/test/webfuse/tests/integration/test_integration.cc +++ b/test/webfuse/tests/integration/test_integration.cc @@ -38,7 +38,7 @@ namespace void SetUp() { server = new Server(); - provider = new Provider("wss://localhost:8080/"); + provider = new Provider(server->GetUrl().c_str()); } void TearDown() diff --git a/test/webfuse/tests/integration/test_lowlevel.cc b/test/webfuse/tests/integration/test_lowlevel.cc index 050b23a..0c49192 100644 --- a/test/webfuse/tests/integration/test_lowlevel.cc +++ b/test/webfuse/tests/integration/test_lowlevel.cc @@ -37,7 +37,7 @@ wf_test_integration_lowlevel_on_disconnected( bool wf_test_integration_lowlevel_authenticate( - struct wf_credentials * credentials, + struct wf_credentials const * credentials, void * ) { char const * username = wf_credentials_get(credentials, "username"); diff --git a/test/webfuse/tests/provider/test_client_protocol.cc b/test/webfuse/tests/provider/test_client_protocol.cc index 2035b0f..e2fe6b6 100644 --- a/test/webfuse/tests/provider/test_client_protocol.cc +++ b/test/webfuse/tests/provider/test_client_protocol.cc @@ -3,20 +3,27 @@ #include #include -#include "webfuse/utils/ws_server.hpp" +#include "webfuse/utils/ws_server.h" #include "webfuse/mocks/mock_provider_client.hpp" +#include "webfuse/core/protocol_names.h" +#include "webfuse/utils/timeout_watcher.hpp" + +#include #include #include #include -using webfuse_test::WebsocketServer; +using webfuse_test::WsServer; using webfuse_test::MockProviderClient; using webfuse_test::IProviderClient; +using webfuse_test::TimeoutWatcher; using testing::_; using testing::AtMost; using testing::Invoke; +#define DEFAULT_TIMEOUT (std::chrono::milliseconds(5 * 1000)) + namespace { @@ -27,29 +34,42 @@ class ClientProtocolFixture public: explicit ClientProtocolFixture(IProviderClient& client, bool enableAuthentication = false) { + server = new WsServer(WF_PROTOCOL_NAME_ADAPTER_SERVER); + config = wfp_client_config_create(); client.AttachTo(config, enableAuthentication); - protocol = wfp_client_protocol_create(config); - struct lws_protocols client_protocol; - memset(&client_protocol, 0, sizeof(struct lws_protocols)); - wfp_client_protocol_init_lws(protocol, &client_protocol); + memset(protocols, 0, sizeof(struct lws_protocols) * 2); + wfp_client_protocol_init_lws(protocol, protocols); - server = new WebsocketServer(54321, &client_protocol, 1); + memset(&info, 0, sizeof(struct lws_context_creation_info)); + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = protocols; + info.uid = -1; + info.gid = -1; + + context = lws_create_context(&info); } ~ClientProtocolFixture() { - delete server; + lws_context_destroy(context); wfp_client_protocol_dispose(protocol); wfp_client_config_dispose(config); + delete server; } void Connect() { - wfp_client_protocol_connect(protocol, server->getContext(), "ws://localhost:54321/"); - server->waitForConnection(); + TimeoutWatcher watcher(DEFAULT_TIMEOUT); + + wfp_client_protocol_connect(protocol, context, server->GetUrl().c_str()); + while (!server->IsConnected()) + { + watcher.check(); + lws_service(context, 0); + } } void Disconnect() @@ -59,19 +79,28 @@ public: void SendToClient(json_t * request) { - server->sendMessage(request); + server->SendMessage(request); } json_t * ReceiveMessageFromClient() { - return server->receiveMessage(); + TimeoutWatcher watcher(DEFAULT_TIMEOUT); + json_t * result = server->ReceiveMessage(); + while (nullptr == result) + { + watcher.check(); + lws_service(context, 0); + result = server->ReceiveMessage(); + } + + return result; } void AwaitAuthentication( std::string const & expected_username, std::string const & expected_password) { - json_t * request = server->receiveMessage(); + json_t * request = ReceiveMessageFromClient(); ASSERT_TRUE(json_is_object(request)); json_t * method = json_object_get(request, "method"); @@ -103,14 +132,14 @@ public: json_t * response = json_object(); json_object_set_new(response, "result", json_object()); json_object_set(response, "id", id); - server->sendMessage(response); + SendToClient(response); json_decref(request); } void AwaitAddFilesystem(std::string& filesystemName) { - json_t * addFilesystemRequest = server->receiveMessage(); + json_t * addFilesystemRequest = ReceiveMessageFromClient(); ASSERT_NE(nullptr, addFilesystemRequest); ASSERT_TRUE(json_is_object(addFilesystemRequest)); @@ -135,15 +164,19 @@ public: json_object_set_new(response, "result", result); json_object_set(response, "id", id); - server->sendMessage(response); + SendToClient(response); json_decref(addFilesystemRequest); } private: - WebsocketServer * server; + WsServer * server; wfp_client_config * config; wfp_client_protocol * protocol; + struct lws_context_creation_info info; + struct lws_protocols protocols[2]; + struct lws_context * context; + }; void GetCredentials(wfp_credentials * credentials) diff --git a/test/webfuse/tests/provider/test_dirbuffer.cc b/test/webfuse/tests/provider/test_dirbuffer.cc new file mode 100644 index 0000000..b4dfe58 --- /dev/null +++ b/test/webfuse/tests/provider/test_dirbuffer.cc @@ -0,0 +1,32 @@ +#include "webfuse/provider/impl/dirbuffer.h" +#include + +TEST(DirBuffer, CreateDispose) +{ + wfp_dirbuffer * buffer = wfp_impl_dirbuffer_create(); + wfp_impl_dirbuffer_dispose(buffer); +} + +TEST(DirBuffer, Add) +{ + wfp_dirbuffer * buffer = wfp_impl_dirbuffer_create(); + wfp_impl_dirbuffer_add(buffer, "answer", 42); + + ASSERT_EQ(1, json_array_size(buffer->entries)); + + json_t * entry = json_array_get(buffer->entries, 0); + ASSERT_STREQ("answer", json_string_value(json_object_get(entry, "name"))); + ASSERT_EQ(42, json_integer_value(json_object_get(entry, "inode"))); + + wfp_impl_dirbuffer_dispose(buffer); +} + +TEST(DirBuffer, Take) +{ + wfp_dirbuffer * buffer = wfp_impl_dirbuffer_create(); + json_t * entries = wfp_impl_dirbuffer_take(buffer); + wfp_impl_dirbuffer_dispose(buffer); + + ASSERT_TRUE(json_is_array(entries)); + json_decref(entries); +} diff --git a/test/webfuse/utils/adapter_client.cc b/test/webfuse/utils/adapter_client.cc new file mode 100644 index 0000000..d1dbb4e --- /dev/null +++ b/test/webfuse/utils/adapter_client.cc @@ -0,0 +1,149 @@ +#include "webfuse/utils/adapter_client.hpp" +#include "webfuse/utils/tempdir.hpp" +#include +#include + +namespace +{ + +enum class Command +{ + run, + shutdown, + connect, + disconnect, + authenticate, + add_filesystem +}; + +} + +namespace webfuse_test +{ + +class AdapterClient::Private +{ +public: + Private( + wf_client_callback_fn * callback, + void * user_data, + std::string const & url) + : client(wf_client_create(callback, user_data)) + , url_(url) + , command(Command::run) + , tempdir("webfuse_adpter_client") + { + thread = std::thread(&Run, this); + } + + ~Private() + { + ApplyCommand(Command::shutdown); + thread.join(); + wf_client_dispose(client); + } + + void ApplyCommand(Command actual_command) + { + { + std::unique_lock lock(mutex); + command = actual_command; + } + + wf_client_interrupt(client); + } + + std::string GetDir() + { + return tempdir.path(); + } + +private: + static void Run(Private * self) + { + bool is_running = true; + while (is_running) + { + Command actual_command; + { + std::unique_lock lock(self->mutex); + actual_command = self->command; + self->command = Command::run; + } + + switch (actual_command) + { + case Command::run: + wf_client_service(self->client); + break; + case Command::connect: + wf_client_connect(self->client, self->url_.c_str()); + break; + case Command::disconnect: + wf_client_disconnect(self->client); + break; + case Command::authenticate: + wf_client_authenticate(self->client); + break; + case Command::add_filesystem: + wf_client_add_filesystem(self->client, self->tempdir.path(), "test"); + break; + case Command::shutdown: + // fall-through + default: + is_running = false; + break; + } + + } + } + + wf_client * client; + std::string url_; + Command command; + TempDir tempdir; + std::thread thread; + std::mutex mutex; +}; + +AdapterClient::AdapterClient( + wf_client_callback_fn * callback, + void * user_data, + std::string const & url) +: d(new Private(callback, user_data, url)) +{ + +} + +AdapterClient::~AdapterClient() +{ + delete d; +} + +void AdapterClient::Connect() +{ + d->ApplyCommand(Command::connect); +} + +void AdapterClient::Disconnect() +{ + d->ApplyCommand(Command::disconnect); +} + +void AdapterClient::Authenticate() +{ + d->ApplyCommand(Command::authenticate); +} + +void AdapterClient::AddFileSystem() +{ + d->ApplyCommand(Command::add_filesystem); +} + +std::string AdapterClient::GetDir() const +{ + return d->GetDir(); +} + + +} \ No newline at end of file diff --git a/test/webfuse/utils/adapter_client.hpp b/test/webfuse/utils/adapter_client.hpp new file mode 100644 index 0000000..ceaefcb --- /dev/null +++ b/test/webfuse/utils/adapter_client.hpp @@ -0,0 +1,32 @@ +#ifndef WF_UTILS_ADAPTER_CLIENT_HPP +#define WF_UTILS_APAPTER_CLIENT_HPP + +#include "webfuse/adapter/client.h" +#include + +namespace webfuse_test +{ + +class AdapterClient +{ + AdapterClient(AdapterClient const &) = delete; + AdapterClient& operator=(AdapterClient const &) = delete; +public: + AdapterClient( + wf_client_callback_fn * callback, + void * user_data, + std::string const & url); + ~AdapterClient(); + void Connect(); + void Disconnect(); + void Authenticate(); + void AddFileSystem(); + std::string GetDir() const; +private: + class Private; + Private * d; +}; + +} + +#endif diff --git a/test/webfuse/utils/jansson_test_environment.cc b/test/webfuse/utils/jansson_test_environment.cc new file mode 100644 index 0000000..24d091f --- /dev/null +++ b/test/webfuse/utils/jansson_test_environment.cc @@ -0,0 +1,16 @@ +#include +#include + +namespace webfuse_test +{ + +class JanssonTestEnvironment: public ::testing::Environment +{ +public: + void SetUp() + { + json_object_seed(0); + } +}; +# +} \ No newline at end of file diff --git a/test/webfuse/utils/tempdir.cc b/test/webfuse/utils/tempdir.cc index 31dce25..46b78a7 100644 --- a/test/webfuse/utils/tempdir.cc +++ b/test/webfuse/utils/tempdir.cc @@ -1,7 +1,7 @@ -#include "webfuse/core/string.h" #include "webfuse/utils/tempdir.hpp" #include +#include #include #include @@ -9,8 +9,8 @@ namespace webfuse_test { TempDir::TempDir(char const * prefix) -: path_(wf_create_string("/tmp/%s_XXXXXX", prefix)) { + asprintf(&path_, "/tmp/%s_XXXXXX", prefix); char * result = mkdtemp(path_); if (NULL == result) { diff --git a/test/webfuse/utils/timeout_watcher.cc b/test/webfuse/utils/timeout_watcher.cc index 8d9add7..705f1f3 100644 --- a/test/webfuse/utils/timeout_watcher.cc +++ b/test/webfuse/utils/timeout_watcher.cc @@ -1,5 +1,6 @@ #include "webfuse/utils/timeout_watcher.hpp" #include +#include using std::chrono::milliseconds; using std::chrono::duration_cast; @@ -41,4 +42,17 @@ void TimeoutWatcher::check() } } +bool TimeoutWatcher::waitUntil(std::function predicate) +{ + bool result = predicate(); + while ((!result) && (!isTimeout())) + { + std::this_thread::yield(); + result = predicate(); + } + + return result; +} + + } \ No newline at end of file diff --git a/test/webfuse/utils/timeout_watcher.hpp b/test/webfuse/utils/timeout_watcher.hpp index 278d25f..3696dd1 100644 --- a/test/webfuse/utils/timeout_watcher.hpp +++ b/test/webfuse/utils/timeout_watcher.hpp @@ -2,6 +2,7 @@ #define WF_TEST_TIMEOUT_WATCHER_HPP #include +#include namespace webfuse_test { @@ -15,6 +16,7 @@ public: ~TimeoutWatcher(); bool isTimeout(); void check(); + bool waitUntil(std::function predicate); private: std::chrono::milliseconds startedAt; std::chrono::milliseconds timeout_; diff --git a/test/webfuse/utils/ws_server.cc b/test/webfuse/utils/ws_server.cc index 48e2588..2c3dcfe 100644 --- a/test/webfuse/utils/ws_server.cc +++ b/test/webfuse/utils/ws_server.cc @@ -1,17 +1,12 @@ -#include "webfuse/utils/ws_server.hpp" -#include "webfuse/utils/timeout_watcher.hpp" +#include "webfuse/utils/ws_server.h" +#include "webfuse/core/lws_log.h" -#include "webfuse/core/util.h" -#include "webfuse/core/protocol_names.h" #include -#include -#include +#include +#include +#include +#include #include -#include - -using webfuse_test::TimeoutWatcher; - -#define DEFAULT_TIMEOUT (std::chrono::milliseconds(5 * 1000)) namespace { @@ -20,10 +15,47 @@ class IServer { public: virtual ~IServer() = default; - virtual void onConnectionEstablished(struct lws * wsi) = 0; - virtual void onConnectionClosed(struct lws * wsi) = 0; - virtual void onMessageReceived(struct lws * wsi, char const * data, size_t length) = 0; - virtual void onWritable(struct lws * wsi) = 0; + virtual void OnConnected(lws * wsi) = 0; + virtual void OnConnectionClosed(lws * wsi) = 0; + virtual void OnMessageReceived(struct lws * wsi, char const * data, size_t length) = 0; + virtual void OnWritable(struct lws * wsi) = 0; +}; + +} + +namespace webfuse_test +{ + +class WsServer::Private : IServer +{ + Private(Private const &) = delete; + Private & operator=(Private const &) = delete; +public: + Private(std::string const & protocol, int port); + ~Private(); + bool IsConnected(); + std::string GetUrl() const; + void SendMessage(json_t * message); + json_t * ReceiveMessage(); + void OnConnected(lws * wsi) override; + void OnConnectionClosed(lws * wsi) override; + void OnMessageReceived(struct lws * wsi, char const * data, size_t length) override; + void OnWritable(struct lws * wsi) override; + +private: + static void run(Private * self); + std::string protocol_; + int port_; + bool is_connected; + bool is_shutdown_requested; + lws * wsi_; + lws_context * ws_context; + lws_protocols ws_protocols[2]; + lws_context_creation_info info; + std::thread context; + std::mutex mutex; + std::queue writeQueue; + std::queue recvQueue; }; } @@ -32,161 +64,169 @@ extern "C" { static int wf_test_utils_ws_server_callback( - struct lws * wsi, - enum lws_callback_reasons reason, - void * WF_UNUSED_PARAM(user), - void * in, - size_t len) + struct lws * wsi, + enum lws_callback_reasons reason, + void * user, + void * in, + size_t len) { + int result = 0; struct lws_protocols const * ws_protocol = lws_get_protocol(wsi); - if (NULL == ws_protocol) + auto * server = reinterpret_cast(nullptr != ws_protocol ? ws_protocol->user : nullptr); + + if (nullptr != server) { - return 0; + switch (reason) + { + case LWS_CALLBACK_ESTABLISHED: + server->OnConnected(wsi); + break; + case LWS_CALLBACK_CLOSED: + server->OnConnectionClosed(wsi); + break; + case LWS_CALLBACK_RECEIVE: + { + auto * data = reinterpret_cast(in); + server->OnMessageReceived(wsi, data, len); + } + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + server->OnWritable(wsi); + break; + default: + break; + } } - auto * server = reinterpret_cast(ws_protocol->user); - switch(reason) - { - case LWS_CALLBACK_ESTABLISHED: - server->onConnectionEstablished(wsi); - break; - case LWS_CALLBACK_CLOSED: - server->onConnectionClosed(wsi); - break; - case LWS_CALLBACK_RECEIVE: - { - auto * data = reinterpret_cast(in); - server->onMessageReceived(wsi, data, len); - } - break; - case LWS_CALLBACK_SERVER_WRITEABLE: - server->onWritable(wsi); - break; - default: - break; - } - - return 0; + return result; } } + namespace webfuse_test { -class WebsocketServer::Private: public IServer +WsServer::WsServer(std::string const & protocol, int port) +: d(new Private(protocol, port)) +{ + +} + +WsServer::~WsServer() +{ + delete d; +} + +bool WsServer::IsConnected() +{ + return d->IsConnected(); +} + +void WsServer::SendMessage(json_t * message) +{ + d->SendMessage(message); +} + +json_t * WsServer::ReceiveMessage() +{ + return d->ReceiveMessage(); +} + +std::string WsServer::GetUrl() const +{ + return d->GetUrl(); +} + + +WsServer::Private::Private(std::string const & protocol, int port) +: protocol_(protocol) +, port_(port) +, is_connected(false) +, is_shutdown_requested(false) +, wsi_(nullptr) +{ + wf_lwslog_disable(); + IServer * server = this; + memset(ws_protocols, 0, sizeof(struct lws_protocols) * 2 ); + + ws_protocols[0].name = protocol_.c_str(); + ws_protocols[0].callback = &wf_test_utils_ws_server_callback; + ws_protocols[0].per_session_data_size = 0; + ws_protocols[0].user = reinterpret_cast(server); + + memset(&info, 0, sizeof(struct lws_context_creation_info)); + info.port = port; + info.mounts = NULL; + info.protocols =ws_protocols; + info.vhost_name = "localhost"; + info.ws_ping_pong_interval = 10; + info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + ws_context = lws_create_context(&info); + + struct lws_vhost * vhost = lws_create_vhost(ws_context, &info); + port_ = lws_get_vhost_port(vhost); + + context = std::thread(&run, this); +} + +WsServer::Private::~Private() { -public: - Private(int port, struct lws_protocols * additionalProtocols, size_t additionalProtocolsCount) - : client_wsi(nullptr) { - ws_protocols = new struct lws_protocols[2 + additionalProtocolsCount]; - memset(ws_protocols, 0, sizeof(struct lws_protocols) * (2 + additionalProtocolsCount)); + std::unique_lock lock(mutex); + is_shutdown_requested = true; + } - ws_protocols[0].name = WF_PROTOCOL_NAME_ADAPTER_SERVER; - ws_protocols[0].callback = &wf_test_utils_ws_server_callback; - ws_protocols[0].per_session_data_size = 0; - ws_protocols[0].user = reinterpret_cast(this); + lws_cancel_service(ws_context); + context.join(); + lws_context_destroy(ws_context); +} - if (0 < additionalProtocolsCount) +void WsServer::Private::run(Private * self) +{ + bool is_running = true; + while (is_running) + { + lws_service(self->ws_context, 0); { - memcpy(&ws_protocols[additionalProtocolsCount], additionalProtocols, sizeof(struct lws_protocols) * additionalProtocolsCount); - } - - memset(&info, 0, sizeof(struct lws_context_creation_info)); - info.port = port; - info.mounts = NULL; - info.protocols =ws_protocols; - info.vhost_name = "localhost"; - info.ws_ping_pong_interval = 10; - info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; - - context = lws_create_context(&info); - - } - - virtual ~Private() - { - lws_context_destroy(context); - delete[] ws_protocols; - } - - struct lws_context * getContext() - { - return context; - } - - void waitForConnection() - { - TimeoutWatcher watcher(DEFAULT_TIMEOUT); - - while (nullptr == client_wsi) - { - watcher.check(); - lws_service(context, 100); + std::unique_lock lock(self->mutex); + is_running = !self->is_shutdown_requested; } } +} - void sendMessage(json_t * message) +bool WsServer::Private::IsConnected() +{ + std::unique_lock lock(mutex); + return is_connected; +} + +void WsServer::Private::OnConnected(lws * wsi) +{ + std::unique_lock lock(mutex); + is_connected = true; + wsi_ = wsi; +} + +void WsServer::Private::OnConnectionClosed(lws * wsi) +{ + std::unique_lock lock(mutex); + if (wsi == wsi_) { - char* message_text = json_dumps(message, JSON_COMPACT); - writeQueue.push(message_text); - json_decref(message); - free(message_text); - - if (nullptr != client_wsi) - { - lws_callback_on_writable(client_wsi); - - TimeoutWatcher watcher(DEFAULT_TIMEOUT); - while (!writeQueue.empty()) - { - watcher.check(); - lws_service(context, 100); - } - } + is_connected = false; + wsi_ = nullptr; } +} + +void WsServer::Private::OnWritable(struct lws * wsi) +{ + bool notify = false; - json_t * receiveMessage() { - TimeoutWatcher watcher(DEFAULT_TIMEOUT); + std::unique_lock lock(mutex); - while (recvQueue.empty()) - { - watcher.check(); - lws_service(context, 100); - } - - std::string const & message_text = recvQueue.front(); - json_t * message = json_loads(message_text.c_str(), JSON_DECODE_ANY, nullptr); - recvQueue.pop(); - - return message; - } - - void onConnectionEstablished(struct lws * wsi) override - { - client_wsi = wsi; - } - - void onConnectionClosed(struct lws * wsi) override - { - if (wsi == client_wsi) - { - client_wsi = nullptr; - } - } - - void onMessageReceived(struct lws * wsi, char const * data, size_t length) override - { - if (wsi == client_wsi) - { - recvQueue.push(std::string(data, length)); - } - } - - void onWritable(struct lws * wsi) override - { if (!writeQueue.empty()) { std::string const & message = writeQueue.front(); @@ -197,69 +237,69 @@ public: delete[] data; writeQueue.pop(); - if (!writeQueue.empty()) - { - lws_callback_on_writable(wsi); - } + notify = !writeQueue.empty(); } } - -private: - void send(std::string const & message) + if (notify) { - if (nullptr != client_wsi) + lws_callback_on_writable(wsi); + } +} + + +void WsServer::Private::SendMessage(json_t * message) +{ + lws * wsi = nullptr; + + { + std::unique_lock lock(mutex); + + if (nullptr != wsi_) { - writeQueue.push(message); - lws_callback_on_writable(client_wsi); + char* message_text = json_dumps(message, JSON_COMPACT); + writeQueue.push(message_text); + json_decref(message); + free(message_text); + wsi = wsi_; } } - struct lws * client_wsi; - - struct lws_protocols * ws_protocols; - struct lws_context_creation_info info; - struct lws_context * context; - std::queue writeQueue; - std::queue recvQueue; - -}; - -WebsocketServer::WebsocketServer(int port) -: d(new Private(port, nullptr, 0)) -{ - + if (nullptr != wsi) + { + lws_callback_on_writable(wsi_); + } } -WebsocketServer::WebsocketServer(int port, struct lws_protocols * additionalProtocols, std::size_t additionalProtocolsCount) -: d(new Private(port, additionalProtocols, additionalProtocolsCount)) +void WsServer::Private::OnMessageReceived(struct lws * wsi, char const * data, size_t length) { - + std::unique_lock lock(mutex); + if (wsi == wsi_) + { + recvQueue.push(std::string(data, length)); + } } -WebsocketServer::~WebsocketServer() +json_t * WsServer::Private::ReceiveMessage() { - delete d; + std::unique_lock lock(mutex); + + json_t * result = nullptr; + if (!recvQueue.empty()) + { + std::string const & message_text = recvQueue.front(); + result = json_loads(message_text.c_str(), JSON_DECODE_ANY, nullptr); + recvQueue.pop(); + } + + return result; } -struct lws_context * WebsocketServer::getContext() +std::string WsServer::Private::GetUrl() const { - return d->getContext(); -} - -void WebsocketServer::waitForConnection() -{ - d->waitForConnection(); -} - -void WebsocketServer::sendMessage(json_t * message) -{ - d->sendMessage(message); -} - -json_t * WebsocketServer::receiveMessage() -{ - return d->receiveMessage(); + std::ostringstream stream; + stream << "ws://localhost:" << port_ << "/"; + return stream.str(); } diff --git a/test/webfuse/utils/ws_server.h b/test/webfuse/utils/ws_server.h new file mode 100644 index 0000000..a51f695 --- /dev/null +++ b/test/webfuse/utils/ws_server.h @@ -0,0 +1,29 @@ +#ifndef WF_TEST_UTILS_WS_SERVER_HPP +#define WF_TEST_UTILS_WS_SERVER_HPP + +#include +#include + +namespace webfuse_test +{ + +class WsServer +{ + WsServer(WsServer const &) = delete; + WsServer & operator=(WsServer const &) = delete; +public: + WsServer(std::string const & protocol, int port = 0); + ~WsServer(); + bool IsConnected(); + std::string GetUrl() const; + void SendMessage(json_t * message); + json_t * ReceiveMessage(); +private: + class Private; + Private * d; +}; + +} + + +#endif diff --git a/test/webfuse/utils/ws_server.hpp b/test/webfuse/utils/ws_server.hpp deleted file mode 100644 index e9d825a..0000000 --- a/test/webfuse/utils/ws_server.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef WF_TEST_UTILS_WS_SERVER_HPP -#define WF_TEST_UTILS_WS_SERVER_HPP - -#include -#include - -namespace webfuse_test -{ - -class WebsocketServer -{ - WebsocketServer(WebsocketServer const &) = delete; - WebsocketServer & operator=(WebsocketServer const &) = delete; -public: - explicit WebsocketServer(int port); - WebsocketServer(int port, struct lws_protocols * additionalProtocols, std::size_t additionalProtocolsCount); - ~WebsocketServer(); - struct lws_context * getContext(); - void waitForConnection(); - void sendMessage(json_t * message); - json_t * receiveMessage(); -private: - class Private; - Private * d; -}; - -} - -#endif diff --git a/test/webfuse/utils/ws_server2.cc b/test/webfuse/utils/ws_server2.cc new file mode 100644 index 0000000..21d4a6e --- /dev/null +++ b/test/webfuse/utils/ws_server2.cc @@ -0,0 +1,331 @@ +#include "webfuse/utils/ws_server2.hpp" +#include "webfuse/core/lws_log.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + +class IServer +{ +public: + virtual ~IServer() = default; + virtual void OnConnected(lws * wsi) = 0; + virtual void OnConnectionClosed(lws * wsi) = 0; + virtual void OnMessageReceived(struct lws * wsi, char const * data, size_t length) = 0; + virtual void OnWritable(struct lws * wsi) = 0; +}; + +} + +extern "C" +{ + +static int wf_test_utils_ws_server_callback( + struct lws * wsi, + enum lws_callback_reasons reason, + void * user, + void * in, + size_t len) +{ + int result = 0; + struct lws_protocols const * ws_protocol = lws_get_protocol(wsi); + auto * server = reinterpret_cast(nullptr != ws_protocol ? ws_protocol->user : nullptr); + + if (nullptr != server) + { + switch (reason) + { + case LWS_CALLBACK_ESTABLISHED: + server->OnConnected(wsi); + break; + case LWS_CALLBACK_CLOSED: + server->OnConnectionClosed(wsi); + break; + case LWS_CALLBACK_RECEIVE: + { + auto * data = reinterpret_cast(in); + server->OnMessageReceived(wsi, data, len); + } + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + server->OnWritable(wsi); + break; + default: + break; + } + } + + return result; +} + +} + + +namespace webfuse_test +{ + +class WsServer2::Private : public IServer +{ + Private(Private const &) = delete; + Private & operator=(Private const &) = delete; +public: + Private(IIvokationHandler & handler, std::string const & protocol, int port, bool enable_tls); + ~Private(); + bool IsConnected(); + std::string const & GetUrl() const; + void OnConnected(lws * wsi) override; + void OnConnectionClosed(lws * wsi) override; + void OnMessageReceived(struct lws * wsi, char const * data, size_t length) override; + void OnWritable(struct lws * wsi) override; + + void SendMessage(char const * message); + void SendMessage(json_t * message); +private: + static void Run(Private * self); + + IIvokationHandler & handler_; + std::string protocol_; + bool is_connected; + bool is_shutdown_requested; + lws * wsi_; + lws_context * ws_context; + lws_protocols ws_protocols[2]; + lws_context_creation_info info; + std::string url; + std::thread context; + std::mutex mutex; + std::queue writeQueue; +}; + +WsServer2::WsServer2( + IIvokationHandler& handler, + std::string const & protocol, + int port, + bool enable_tls) +: d(new WsServer2::Private(handler, protocol, port, enable_tls)) +{ + +} + +WsServer2::~WsServer2() +{ + delete d; +} + +bool WsServer2::IsConnected() +{ + return d->IsConnected(); +} + +std::string const & WsServer2::GetUrl() const +{ + return d->GetUrl(); +} + +void WsServer2::SendMessage(char const * message) +{ + d->SendMessage(message); +} + +void WsServer2::SendMessage(json_t * message) +{ + d->SendMessage(message); +} + + +WsServer2::Private::Private( + IIvokationHandler & handler, + std::string const & protocol, + int port, + bool enable_tls) +: handler_(handler) +, protocol_(protocol) +, is_connected(false) +, is_shutdown_requested(false) +, wsi_(nullptr) +{ + wf_lwslog_disable(); + IServer * server = this; + memset(ws_protocols, 0, sizeof(struct lws_protocols) * 2 ); + + ws_protocols[0].name = protocol_.c_str(); + ws_protocols[0].callback = &wf_test_utils_ws_server_callback; + ws_protocols[0].per_session_data_size = 0; + ws_protocols[0].user = reinterpret_cast(server); + + memset(&info, 0, sizeof(struct lws_context_creation_info)); + info.port = port; + info.mounts = NULL; + info.protocols =ws_protocols; + info.vhost_name = "localhost"; + info.ws_ping_pong_interval = 10; + info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + if (enable_tls) + { + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.ssl_cert_filepath = "server-cert.pem"; + info.ssl_private_key_filepath = "server-key.pem"; + } + + ws_context = lws_create_context(&info); + + std::ostringstream stream; + struct lws_vhost * vhost = lws_create_vhost(ws_context, &info); + stream << (enable_tls ? "wss://" : "ws://") + << "localhost:" << lws_get_vhost_port(vhost) << "/"; + url = stream.str(); + + context = std::thread(&Run, this); +} + +WsServer2::Private::~Private() +{ + { + std::unique_lock lock(mutex); + is_shutdown_requested = true; + } + + lws_cancel_service(ws_context); + context.join(); + lws_context_destroy(ws_context); +} + +void WsServer2::Private::Run(Private * self) +{ + bool is_running = true; + while (is_running) + { + lws_service(self->ws_context, 0); + { + std::unique_lock lock(self->mutex); + is_running = !self->is_shutdown_requested; + } + } +} + +bool WsServer2::Private::IsConnected() +{ + std::unique_lock lock(mutex); + return is_connected; +} + +void WsServer2::Private::OnConnected(lws * wsi) +{ + std::unique_lock lock(mutex); + is_connected = true; + wsi_ = wsi; +} + +void WsServer2::Private::OnConnectionClosed(lws * wsi) +{ + std::unique_lock lock(mutex); + if (wsi == wsi_) + { + is_connected = false; + wsi_ = nullptr; + } +} + +void WsServer2::Private::OnWritable(struct lws * wsi) +{ + bool notify = false; + + { + std::unique_lock lock(mutex); + + if (!writeQueue.empty()) + { + std::string const & message = writeQueue.front(); + + unsigned char * data = new unsigned char[LWS_PRE + message.size()]; + memcpy(&data[LWS_PRE], message.c_str(), message.size()); + lws_write(wsi, &data[LWS_PRE], message.size(), LWS_WRITE_TEXT); + delete[] data; + + writeQueue.pop(); + notify = !writeQueue.empty(); + } + } + + if (notify) + { + lws_callback_on_writable(wsi); + } +} + +void WsServer2::Private::SendMessage(char const * message) +{ + lws * wsi = nullptr; + + { + std::unique_lock lock(mutex); + + if (nullptr != wsi_) + { + writeQueue.push(message); + wsi = wsi_; + } + } + + if (nullptr != wsi) + { + lws_callback_on_writable(wsi_); + } +} + +void WsServer2::Private::SendMessage(json_t * message) +{ + char* message_text = json_dumps(message, JSON_COMPACT); + SendMessage(message_text); + json_decref(message); + free(message_text); +} + +void WsServer2::Private::OnMessageReceived(struct lws * wsi, char const * data, size_t length) +{ + (void) wsi; + + json_t * request = json_loadb(data, length, JSON_DECODE_ANY, nullptr); + json_t * method = json_object_get(request, "method"); + json_t * params = json_object_get(request, "params"); + json_t * id = json_object_get(request, "id"); + + if (json_is_string(method) && json_is_array(params) && json_is_integer(id)) + { + json_t * response = json_object(); + + try + { + std::string result_text = handler_.Invoke(json_string_value(method), params); + json_t * result = json_loads(result_text.c_str(), JSON_DECODE_ANY, nullptr); + json_object_set_new(response, "result", result); + } + catch (...) + { + json_t * error = json_object(); + json_object_set_new(error, "code", json_integer(1)); + json_object_set_new(response, "error", error); + } + + json_object_set(response, "id", id); + SendMessage(response); + } + + json_decref(request); +} + +std::string const & WsServer2::Private::GetUrl() const +{ + return url; +} + + +} diff --git a/test/webfuse/utils/ws_server2.hpp b/test/webfuse/utils/ws_server2.hpp new file mode 100644 index 0000000..b29ffb1 --- /dev/null +++ b/test/webfuse/utils/ws_server2.hpp @@ -0,0 +1,39 @@ +#ifndef WF_TEST_UTILS_WS_SERVER2_HPP +#define WF_TEST_UTILS_WS_SERVER2_HPP + +#include +#include + +namespace webfuse_test +{ + +class IIvokationHandler +{ +public: + virtual ~IIvokationHandler() = default; + virtual std::string Invoke(char const * method, json_t * params) = 0; +}; + +class WsServer2 +{ + WsServer2(WsServer2 const &) = delete; + WsServer2 & operator=(WsServer2 const & ) = delete; +public: + WsServer2( + IIvokationHandler& handler, + std::string const & protocol, + int port = 0, + bool enable_tls = false); + virtual ~WsServer2(); + bool IsConnected(); + std::string const & GetUrl() const; + void SendMessage(char const * message); + void SendMessage(json_t * message); +private: + class Private; + Private * d; +}; + +} + +#endif