diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a37ff9ed..36040e2d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,6 +354,7 @@ endif() if (LWS_WITH_GENERIC_SESSIONS) set(LWS_WITH_SQLITE3 1) set(LWS_WITH_SMTP 1) + set(LWS_WITH_STRUCT_SQLITE3 1) endif() if (LWS_WITH_ESP32) @@ -655,6 +656,13 @@ if ("${LWS_MAX_SMP}" STREQUAL "") set(LWS_MAX_SMP 1) endif() +# using any abstract protocol enables LWS_WITH_ABSTRACT + +if (LWS_WITH_SMTP) + set(LWS_WITH_ABSTRACT 1) +endif() + + if (MINGW) set(LWS_MINGW_SUPPORT 1) @@ -916,8 +924,11 @@ if (LWS_WITH_NETWORK) lib/core-net/wsi-timeout.c lib/core-net/adopt.c lib/roles/pipe/ops-pipe.c - lib/abstract/abstract.c ) + if (LWS_WITH_ABSTRACT) + list(APPEND SOURCES + lib/abstract/abstract.c) + endif() if (LWS_WITH_STATS) list(APPEND SOURCES @@ -970,9 +981,13 @@ endif() if (LWS_ROLE_RAW) list(APPEND SOURCES - lib/abstract/transports/raw-skt.c lib/roles/raw-skt/ops-raw-skt.c lib/roles/raw-file/ops-raw-file.c) + + if (LWS_WITH_ABSTRACT) + list(APPEND SOURCES + lib/abstract/transports/raw-skt.c) + endif() endif() if (LWS_ROLE_RAW_PROXY) @@ -1282,9 +1297,14 @@ if (LWS_WITH_LEJP_CONF AND LWS_WITH_NETWORK AND NOT LWS_PLAT_OPTEE) ) endif() +if (LWS_WITH_ABSTRACT) + list(APPEND SOURCES + lib/abstract/transports/unit-test.c) +endif() + if (LWS_WITH_SMTP) list(APPEND SOURCES - lib/abstract/smtp/smtp.c) + lib/abstract/protocols/smtp/smtp.c) endif() if (LWS_WITH_RANGES) @@ -2602,6 +2622,7 @@ message(" LWS_HAVE__ATOI64 = ${LWS_HAVE__ATOI64}") message(" LWS_HAVE_STAT32I64 = ${LWS_HAVE_STAT32I64}") message(" LWS_HAS_INTPTR_T = ${LWS_HAS_INTPTR_T}") message(" LWS_WITH_EXPORT_LWSTARGETS = ${LWS_WITH_EXPORT_LWSTARGETS}") +message(" LWS_WITH_ABSTRACT = ${LWS_WITH_ABSTRACT}") message("---------------------------------------------------------------------") diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index d572b0b90..0e1650805 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -79,6 +79,7 @@ #cmakedefine LWS_SHA1_USE_OPENSSL_NAME #cmakedefine LWS_SSL_CLIENT_USE_OS_CA_CERTS #cmakedefine LWS_SSL_SERVER_WITH_ECDH_CERT +#cmakedefine LWS_WITH_ABSTRACT #cmakedefine LWS_WITH_ACCESS_LOG #cmakedefine LWS_WITH_ACME #cmakedefine LWS_WITH_BORINGSSL diff --git a/doc-assets/abstract-overview.svg b/doc-assets/abstract-overview.svg index 4da583c1b..db1ac4733 100644 --- a/doc-assets/abstract-overview.svg +++ b/doc-assets/abstract-overview.svg @@ -1,7 +1,22 @@ - + + + + + + + + + + + + + + + + @@ -18,208 +33,316 @@ - - - - - - + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/libwebsockets.h b/include/libwebsockets.h index aa4057868..92888c1b5 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -532,8 +532,7 @@ struct lws; #include #include -#include -#include +#include #if defined(LWS_WITH_TLS) diff --git a/include/libwebsockets/abstract/abstract.h b/include/libwebsockets/abstract/abstract.h new file mode 100644 index 000000000..d6c56a1e4 --- /dev/null +++ b/include/libwebsockets/abstract/abstract.h @@ -0,0 +1,120 @@ +/* + * libwebsockets - abstract top level header + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +/* + * These are used to optionally pass an array of index = C string, binary array, + * or ulong tokens to the abstract transport or protocol. For example if it's + * raw socket transport, then the DNS address to connect to and the port are + * passed using these when the client created and bound to the transport. + */ + +typedef struct lws_token_map { + union { + const char *value; + uint8_t *bvalue; + unsigned long lvalue; + } u; + short name_index; /* 0 here indicates end of array */ + short length_or_zero; +} lws_token_map_t; + +/* + * The indvidual protocols and transports define their own name_index-es which + * are meaningful to them. Define index 0 globally as the end of an array of + * them, and separate the ones used for protocols and transport so we can + * sanity check they are at least in the correct category. + */ + +enum { + LTMI_END_OF_ARRAY, + + LTMI_PROTOCOL_BASE = 2048, + + LTMI_TRANSPORT_BASE = 4096 +}; + +struct lws_abs_transport; +struct lws_abs_protocol; + +LWS_VISIBLE LWS_EXTERN const lws_token_map_t * +lws_abs_get_token(const lws_token_map_t *token_map, short name_index); + +/* + * the combination of a protocol, transport, and token maps for each + */ + +typedef void lws_abs_transport_inst_t; +typedef void lws_abs_protocol_inst_t; + +typedef struct lws_abs { + void *user; + struct lws_vhost *vh; + + const struct lws_abs_protocol *ap; + const lws_token_map_t *ap_tokens; + const struct lws_abs_transport *at; + const lws_token_map_t *at_tokens; + + /* + * These are filled in by lws_abs_bind_and_create_instance() in the + * instance copy. They do not need to be set when creating the struct + * for use by lws_abs_bind_and_create_instance() + */ + + struct lws_dll2 abstract_instances; + lws_abs_transport_inst_t *ati; + lws_abs_protocol_inst_t *api; +} lws_abs_t; + +/** + * lws_abs_bind_and_create_instance - use an abstract protocol and transport + * + * \param abs: the lws_abs_t describing the combination desired + * + * This instantiates an abstract protocol and abstract transport bound together. + * A single heap allocation is made for the combination and the protocol and + * transport creation ops are called on it. The ap_tokens and at_tokens + * are consulted by the creation ops to decide the details of the protocol and + * transport for the instance. + */ +LWS_VISIBLE LWS_EXTERN lws_abs_t * +lws_abs_bind_and_create_instance(const lws_abs_t *ai); + +/** + * lws_abs_destroy_instance() - destroys an instance + * + * \param ai: pointer to the ai pointer to destroy + * + * This is for destroying an instance created by + * lws_abs_bind_and_create_instance() above. + * + * Calls the protocol and transport destroy operations on the instance, then + * frees the combined allocation in one step. The pointer ai is set to NULL. + */ +LWS_VISIBLE LWS_EXTERN void +lws_abs_destroy_instance(lws_abs_t **ai); + +/* + * bring in all the protocols and transports definitions + */ + +#include +#include diff --git a/include/libwebsockets/abstract/protocols.h b/include/libwebsockets/abstract/protocols.h new file mode 100644 index 000000000..a6f802a29 --- /dev/null +++ b/include/libwebsockets/abstract/protocols.h @@ -0,0 +1,53 @@ +/* + * libwebsockets - abstract protocol definitions + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +typedef struct lws_abs_protocol { + const char *name; + int alloc; + + int (*create)(const struct lws_abs *ai); + void (*destroy)(lws_abs_protocol_inst_t **d); + + /* events the transport invokes (handled by abstract protocol) */ + + int (*accept)(lws_abs_protocol_inst_t *d); + int (*rx)(lws_abs_protocol_inst_t *d, uint8_t *buf, size_t len); + int (*writeable)(lws_abs_protocol_inst_t *d, size_t budget); + int (*closed)(lws_abs_protocol_inst_t *d); + int (*heartbeat)(lws_abs_protocol_inst_t *d); +} lws_abs_protocol_t; + +/** + * lws_abs_protocol_get_by_name() - returns a pointer to the named protocol ops + * + * \param name: the name of the abstract protocol + * + * Returns a pointer to the named protocol ops struct if available, otherwise + * NULL. + */ +LWS_VISIBLE LWS_EXTERN const lws_abs_protocol_t * +lws_abs_protocol_get_by_name(const char *name); + +/* + * bring in public api pieces from protocols + */ + +#include diff --git a/include/libwebsockets/abstract/smtp.h b/include/libwebsockets/abstract/protocols/smtp.h similarity index 73% rename from include/libwebsockets/abstract/smtp.h rename to include/libwebsockets/abstract/protocols/smtp.h index 7ba5439fb..5fb434e4a 100644 --- a/include/libwebsockets/abstract/smtp.h +++ b/include/libwebsockets/abstract/protocols/smtp.h @@ -54,26 +54,16 @@ //@{ #if defined(LWS_WITH_SMTP) +enum { + LTMI_PSMTP_V_HELO = LTMI_PROTOCOL_BASE, /* u.value */ + LTMI_PSMTP_LV_RETRY_INTERVAL, /* u.lvalue */ + LTMI_PSMTP_LV_DELIVERY_TIMEOUT, /* u.lvalue */ + LTMI_PSMTP_LV_EMAIL_QUEUE_MAX, /* u.lvalue */ + LTMI_PSMTP_LV_MAX_CONTENT_SIZE, /* u.lvalue */ +}; + typedef struct lws_smtp_client lws_smtp_client_t; -typedef struct lws_abstract lws_abstract_t; - -typedef struct lws_smtp_client_info { - void *data; - - char helo[32]; /**< Fill before init, eg, "myserver.com" */ - - const lws_abstract_t *abs; /**< abstract transport to use */ - const lws_token_map_t *abs_tokens; /**< transport-specific metadata - for this particular - connection */ - struct lws_vhost *vh; - - time_t retry_interval; - time_t delivery_timeout; - - size_t email_queue_max; - size_t max_content_size; -} lws_smtp_client_info_t; +typedef struct lws_abs lws_abs_t; typedef struct lws_smtp_email { struct lws_dll2 list; @@ -94,17 +84,6 @@ typedef struct lws_smtp_email { } lws_smtp_email_t; -/** - * lws_smtp_client_create() - Initialize a struct lws_email - * - * \param abs: abstract transport to use with the new SMTP client - * \param ci: parameters describing the new SMTP client characteristics - * - * Prepares a struct lws_email for use ending SMTP - */ -LWS_VISIBLE LWS_EXTERN lws_smtp_client_t * -lws_smtp_client_create(const lws_smtp_client_info_t *ci); - /** * lws_smtp_client_alloc_email_helper() - Allocates and inits an email object * @@ -133,34 +112,23 @@ lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len, /** * lws_smtp_client_add_email() - Add email to the list of ones being sent * - * \param c: smtp client + * \param instance: smtp client + transport * \param e: email to queue for sending on \p c * * Adds an email to the linked-list of emails to send */ LWS_VISIBLE LWS_EXTERN int -lws_smtp_client_add_email(lws_smtp_client_t *c, lws_smtp_email_t *e); +lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e); /** * lws_smtp_client_kick() - Request check for new email * - * \param email: lws_smtp_client_t context to kick + * \param instance: instance to kick * * Gives smtp client a chance to move things on */ LWS_VISIBLE LWS_EXTERN void -lws_smtp_client_kick(lws_smtp_client_t *email); - -/** - * lws_smtp_client_destroy() - stop using the struct lws_email - * - * \param email: the lws_smtp_client_t context - * - * Stop sending email using email and free allocations - */ -LWS_VISIBLE LWS_EXTERN void -lws_smtp_client_destroy(lws_smtp_client_t **email); - +lws_smtp_client_kick(lws_abs_t *instance); #endif //@} diff --git a/include/libwebsockets/abstract/transports.h b/include/libwebsockets/abstract/transports.h index 0d9dbf4d9..e0aebc311 100644 --- a/include/libwebsockets/abstract/transports.h +++ b/include/libwebsockets/abstract/transports.h @@ -21,83 +21,41 @@ * included from libwebsockets.h */ -typedef void lws_abs_user_t; -typedef void lws_abs_t; - /* - * These are used to optionally pass an array of index = C string or binary - * array tokens to the abstract transport. For example if it's raw socket - * transport, then the DNS address to connect to and the port are passed using - * these when the client created and bound to the transport. + * Abstract transport ops */ -typedef struct lws_token_map { - union { - const char *value; - uint8_t *bvalue; - unsigned long lvalue; - } u; - short name_index; /* 0 here indicates end of array */ - short length_or_zero; -} lws_token_map_t; - -enum { - LTMI_END_OF_ARRAY, - - LTMI_PEER_DNS_ADDRESS, /* u.value */ - LTMI_PEER_PORT, /* u.lvalue */ - LTMI_PEER_TLS_FLAGS, /* u.lvalue */ -}; - -/* - * The abstract callbacks are in three parts - * - * - create and destroy - * - * - events handled by the transport - * - * - events handled by the user of the transport - * - * the canned abstract transports only define the first two types... the - * remaining callbacks must be filled in to callback functions specific to - * the user of the abstract transport. - * - * This abi has to be public so the user can create their own private abstract - * transports. - */ - -typedef struct lws_abstract { - +typedef struct lws_abs_transport { const char *name; + int alloc; - lws_abs_user_t * (*create)(struct lws_abstract *abs, void *user); - void (*destroy)(lws_abs_user_t **d); + int (*create)(struct lws_abs *abs); + void (*destroy)(lws_abs_transport_inst_t **d); - /* events the abstract object invokes (filled in by transport) */ - int (*tx)(lws_abs_user_t *d, uint8_t *buf, size_t len); - int (*client_conn)(lws_abs_user_t *d, struct lws_vhost *vh, - const lws_token_map_t *token_map); - int (*close)(lws_abs_user_t *d); - int (*ask_for_writeable)(lws_abs_user_t *d); - int (*set_timeout)(lws_abs_user_t *d, int reason, int secs); - int (*state)(lws_abs_user_t *d); + /* events the abstract protocol invokes (handled by transport) */ - /* events the transport invokes (filled in by abstract object) */ + int (*tx)(lws_abs_transport_inst_t *d, uint8_t *buf, size_t len); + int (*client_conn)(const lws_abs_t *abs); + int (*close)(lws_abs_transport_inst_t *d); + int (*ask_for_writeable)(lws_abs_transport_inst_t *d); + int (*set_timeout)(lws_abs_transport_inst_t *d, int reason, int secs); + int (*state)(lws_abs_transport_inst_t *d); +} lws_abs_transport_t; - int (*accept)(lws_abs_user_t *d); - int (*rx)(lws_abs_user_t *d, uint8_t *buf, size_t len); - int (*writeable)(lws_abs_user_t *d, size_t budget); - int (*closed)(lws_abs_user_t *d); - int (*heartbeat)(lws_abs_user_t *d); +/** + * lws_abs_protocol_get_by_name() - returns a pointer to the named protocol ops + * + * \param name: the name of the abstract protocol + * + * Returns a pointer to the named protocol ops struct if available, otherwise + * NULL. + */ +LWS_VISIBLE LWS_EXTERN const lws_abs_transport_t * +lws_abs_transport_get_by_name(const char *name); -} lws_abstract_t; +/* + * bring in public api pieces from transports + */ - -LWS_VISIBLE LWS_EXTERN void -lws_abstract_copy(lws_abstract_t *dest, const lws_abstract_t *src); - -LWS_VISIBLE LWS_EXTERN const lws_abstract_t * -lws_abstract_get_by_name(const char *name); - -LWS_VISIBLE LWS_EXTERN const lws_token_map_t * -lws_abstract_get_token(const lws_token_map_t *token_map, short name_index); +#include +#include diff --git a/include/libwebsockets/abstract/transports/raw-skt.h b/include/libwebsockets/abstract/transports/raw-skt.h new file mode 100644 index 000000000..f35ecafe8 --- /dev/null +++ b/include/libwebsockets/abstract/transports/raw-skt.h @@ -0,0 +1,26 @@ +/* + * libwebsockets - raw-skt abstract transport + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +enum { + LTMI_PEER_V_DNS_ADDRESS = LTMI_TRANSPORT_BASE, /* u.value */ + LTMI_PEER_LV_PORT, /* u.lvalue */ + LTMI_PEER_LV_TLS_FLAGS, /* u.lvalue */ +}; diff --git a/include/libwebsockets/abstract/transports/unit-test.h b/include/libwebsockets/abstract/transports/unit-test.h new file mode 100644 index 000000000..da96a313e --- /dev/null +++ b/include/libwebsockets/abstract/transports/unit-test.h @@ -0,0 +1,54 @@ +/* + * libwebsockets include/libwebsockets/abstract/transports/unit-test.c + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +enum { + LWS_AUT_EXPECT_TEST_END = (1 << 0), + LWS_AUT_EXPECT_LOCAL_CLOSE = (1 << 1), + LWS_AUT_EXPECT_DO_REMOTE_CLOSE = (1 << 2), + LWS_AUT_EXPECT_TX /* expect this as tx from protocol */ = (1 << 3), + LWS_AUT_EXPECT_RX /* present this as rx to protocol */ = (1 << 4), +}; + +typedef enum { + LPE_CONTINUE, + LPE_SUCCEEDED, + LPE_FAILED, +} lws_expect_disposition; + +typedef struct lws_expect { + void *buffer; + size_t len; + + uint32_t flags; +} lws_expect_t; + +typedef int (*lws_expect_test_instance_init)(lws_abs_t *instance); + +typedef struct lws_expect_test { + const char *name; /* NULL indicates end of test array */ + lws_expect_t *expect; + lws_expect_test_instance_init *init; +} lws_expect_test_t; + +enum { + LTMI_PEER_V_EXPECT_TEST = LTMI_TRANSPORT_BASE, /* u.value */ + LTMI_PEER_V_EXPECT_TEST_ARRAY, /* u.value */ +}; diff --git a/lib/abstract/README.md b/lib/abstract/README.md index ae27df07c..91a84bc62 100644 --- a/lib/abstract/README.md +++ b/lib/abstract/README.md @@ -11,11 +11,21 @@ details from protocol specification, lws now supports ![lws_abstract overview](/doc-assets/abstract-overview.svg) -The concept is that the abstract protocol implementation only -operates on callback events and reads and writes to buffers... -separately when it is instantiated, it can be bound to an -"abstract transport" which handles all the details of sending -and receiving on whatever the transport is. +The concept is that the implementation is split into two separate +chunks of code hidden behind "ops" structs... the "abstract protocol" +implementation is responsible for the logical protocol operation +and reads and writes only memory buffers. + +The "abstract transport" implementation is responsible for sending +and receiving buffers on some kind of transport, and again is hidden +behind a standardized ops struct. + +In the system, both the abstract protocols and transports are +found by their name. + +An actual "connection" is created by calling a generic api +`lws_abs_bind_and_create_instance()` to instantiate the +combination of a protocol and a transport. This makes it possible to confidently offer the same protocol on completely different transports, eg, like serial, or to wire @@ -25,26 +35,42 @@ any network. The abstract protocol itself has no relationship to the transport at all and is completely unchanged by changes to the transport. +In addition, generic tokens to control settings in both the +protocol and the transport are passed in at instantiation-time, +eg, controlling the IP address targeted by the transport. + lws SMTP client support has been rewritten to use the new scheme, and lws provides a raw socket transport built-in. ## Public API The public api for defining abstract protocols and transports is -found at [transports.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/transports.h) +found at -### `lws_abstract_t` + - [abstract.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/abstract.h) + - [protocols.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/protocols.h) + - [transports.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/transports.h) -The main structure that defines the abstraction is `lws_abstract_t`, -this is a name and then about a dozen function pointers for various -events and operations. +### `lws_abs_t` -The transport defines about half of these and exports this -`lws_abstract_t *` via its name, it can be retreived using +The main structure that defines the abstraction is `lws_abs_t`, +this is a name and then pointers to the protocol and transport, +optional tokens to control both the protocol and transport, +and pointers to private allocations for both the +protocol and transport when instantiated. + +The transport is selected using ``` -LWS_VISIBLE LWS_EXTERN const lws_abstract_t * -lws_abstract_get_by_name(const char *name); +LWS_VISIBLE LWS_EXTERN const lws_abs_transport_t * +lws_abs_transport_get_by_name(const char *name); +``` + +and similarly the protocol by + +``` +LWS_VISIBLE LWS_EXTERN const lws_abs_protocol_t * +lws_abs_protocol_get_by_name(const char *name); ``` At the moment only "`raw-skt`" is defined as an lws built-in, athough @@ -63,11 +89,6 @@ test jigs. These are called by the protocol to get things done and make queries through the abstract transport. -When you instantiate an abstract protocol, it defines the other half of -the `lws_abstract_t` operations and is combined with the transport -`lws_abstract_t *` to get the full set of operations necessary for the -protocol to operate on the transport. - |protocol op|meaning| |---|---| |`accept()`|The peer has accepted the transport connection| @@ -79,6 +100,12 @@ protocol to operate on the transport. These are called by the transport to inform the protocol of events and traffic. +### Instantiation + +The user fills an lws_abs_t and passes a pointer to it to +`lws_abs_bind_and_create_instance()` to create an instantiation +of the protocol + transport. + ### `lws_token_map_t` The abstract protocol has no idea about a network or network addresses @@ -101,3 +128,28 @@ static const lws_token_map_t smtp_abs_tokens[] = { }}; ``` +## Steps for adding new abstract protocols + + - add the public header in `./include/libwebsockets/abstract/protocols/` + - add a directory under `./lib/abstract/protocols/` + - add your protocol sources in the new directory + - in CMakeLists.txt: + - add an `LWS_WITH_xxx` for your protocol + - search for "using any abstract protocol" and add your `LWS_WITH_xxx` to + the if so it also sets `LWS_WITH_ABSTRACT` if any set + - add a clause to append your source to SOURCES if `LWS_WITH_xxx` enabled + - add your `lws_abs_protocol` to the list `available_abs_protocols` in + `./lib/abstract/abstract.c` + +## Steps for adding new abstract transports + + - add the public header in `./include/libwebsockets/abstract/transports/` + - add your transport sources under `./lib/abstract/transports/` + - in CMakeLists.txt append your transport sources to SOURCES if `LWS_WITH_ABSTRACT` + and any other cmake conditionals + - add an extern for your transport `lws_protocols` in `./lib/core-net/private.h` + - add your transport `lws_protocols` to `available_abstract_protocols` in + `./lib/core-net/vhost.c` + - add your `lws_abs_transport` to the list `available_abs_transports` in + `./lib/abstract/abstract.c` + diff --git a/lib/abstract/abstract.c b/lib/abstract/abstract.c index 52d7b5787..68dcf54ef 100644 --- a/lib/abstract/abstract.c +++ b/lib/abstract/abstract.c @@ -22,37 +22,57 @@ #include #include -extern lws_abstract_t lws_abstract_transport_cli_raw_skt; +extern const lws_abs_transport_t lws_abs_transport_cli_raw_skt, + lws_abs_transport_cli_unit_test; +#if defined(LWS_WITH_SMTP) +extern const lws_abs_protocol_t lws_abs_protocol_smtp; +#endif -static const lws_abstract_t *available_abstractions[] = { - &lws_abstract_transport_cli_raw_skt, +static const lws_abs_transport_t * const available_abs_transports[] = { + &lws_abs_transport_cli_raw_skt, + &lws_abs_transport_cli_unit_test, }; -/* - * the definition is opaque, so a helper to copy it into place - */ +/* HACK: microsoft compiler can't handle zero length array definition */ +#if defined(LWS_WITH_SMTP) +static const lws_abs_protocol_t * const available_abs_protocols[] = { +#if defined(LWS_WITH_SMTP) + &lws_abs_protocol_smtp, +#endif +}; +#endif -void -lws_abstract_copy(lws_abstract_t *dest, const lws_abstract_t *src) -{ - memcpy(dest, src, sizeof(*dest)); -} - - -const lws_abstract_t * -lws_abstract_get_by_name(const char *name) +const lws_abs_transport_t * +lws_abs_transport_get_by_name(const char *name) { int n; - for (n = 0; n < (int)LWS_ARRAY_SIZE(available_abstractions); n++) - if (!strcmp(name, available_abstractions[n]->name)) - return available_abstractions[n]; + for (n = 0; n < (int)LWS_ARRAY_SIZE(available_abs_transports); n++) + if (!strcmp(name, available_abs_transports[n]->name)) + return available_abs_transports[n]; + + lwsl_err("%s: cannot find '%s'\n", __func__, name); + + return NULL; +} + +const lws_abs_protocol_t * +lws_abs_protocol_get_by_name(const char *name) +{ +#if defined(LWS_WITH_SMTP) + int n; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(available_abs_protocols); n++) + if (!strcmp(name, available_abs_protocols[n]->name)) + return available_abs_protocols[n]; +#endif + lwsl_err("%s: cannot find '%s'\n", __func__, name); return NULL; } const lws_token_map_t * -lws_abstract_get_token(const lws_token_map_t *token_map, short name_index) +lws_abs_get_token(const lws_token_map_t *token_map, short name_index) { if (!token_map) return NULL; @@ -65,3 +85,61 @@ lws_abstract_get_token(const lws_token_map_t *token_map, short name_index) return NULL; } + +void +lws_abs_destroy_instance(lws_abs_t **ai) +{ + if ((*ai)->api) + (*ai)->ap->destroy(&(*ai)->api); + if ((*ai)->ati) + (*ai)->at->destroy(&(*ai)->ati); + + lws_dll2_remove(&(*ai)->abstract_instances); + + free(*ai); + *ai = NULL; +} + +lws_abs_t * +lws_abs_bind_and_create_instance(const lws_abs_t *abs) +{ + size_t size = sizeof(lws_abs_t) + abs->ap->alloc + abs->at->alloc; + lws_abs_t *ai; + + /* + * since we know we will allocate the lws_abs_t, the protocol's + * instance allocation, and the transport's instance allocation, + * we merge it into a single heap allocation + */ + ai = lws_malloc(size, "abs inst"); + if (!ai) + return NULL; + + *ai = *abs; + ai->ati = NULL; + + ai->api = (char *)ai + sizeof(lws_abs_t); + if (ai->ap->create(ai)) { + ai->api = NULL; + goto bail; + } + + ai->ati = (char *)ai->api + abs->ap->alloc; + if (ai->at->create(ai)) { + ai->ati = NULL; + goto bail; + } + + /* add us to the vhost's dll2 of instances */ + + lws_dll2_clear(&ai->abstract_instances); + lws_dll2_add_head(&ai->abstract_instances, + &ai->vh->abstract_instances_owner); + + return ai; + +bail: + lws_abs_destroy_instance(&ai); + + return NULL; +} diff --git a/lib/abstract/smtp/smtp.c b/lib/abstract/protocols/smtp/smtp.c similarity index 58% rename from lib/abstract/smtp/smtp.c rename to lib/abstract/protocols/smtp/smtp.c index 454d9afc3..f0444f62d 100644 --- a/lib/abstract/smtp/smtp.c +++ b/lib/abstract/protocols/smtp/smtp.c @@ -20,7 +20,40 @@ */ #include "core/private.h" -#include "abstract/smtp/private.h" +#include "abstract/private.h" + +/** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */ +typedef enum lwsgs_smtp_states { + LGSSMTP_IDLE, /**< awaiting new email */ + LGSSMTP_CONNECTING, /**< opening tcp connection to MTA */ + LGSSMTP_CONNECTED, /**< tcp connection to MTA is connected */ + LGSSMTP_SENT_HELO, /**< sent the HELO */ + LGSSMTP_SENT_FROM, /**< sent FROM */ + LGSSMTP_SENT_TO, /**< sent TO */ + LGSSMTP_SENT_DATA, /**< sent DATA request */ + LGSSMTP_SENT_BODY, /**< sent the email body */ + LGSSMTP_SENT_QUIT, /**< sent the session quit */ +} lwsgs_smtp_states_t; + +/** struct lws_email - abstract context for performing SMTP operations */ +typedef struct lws_smtp_client { + struct lws_dll2_owner pending_owner; + + const struct lws_abs *abs; + + const char *helo; + + lwsgs_smtp_states_t estate; + time_t email_connect_started; + + time_t retry_interval; + time_t delivery_timeout; + + size_t email_queue_max; + size_t max_content_size; + + unsigned char send_pending:1; +} lws_smtp_client_t; static const short retcodes[] = { 0, /* idle */ @@ -41,8 +74,8 @@ lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s) c->estate = s; } -void -lws_smtp_client_kick(lws_smtp_client_t *c) +static void +lws_smtp_client_kick_internal(lws_smtp_client_t *c) { lws_smtp_email_t *e; lws_dll2_t *d; @@ -63,7 +96,7 @@ again: /* do we need to time out this guy? */ - if ((time_t)lws_now_secs() - e->added > (time_t)c->i.delivery_timeout) { + if ((time_t)lws_now_secs() - e->added > (time_t)c->delivery_timeout) { lwsl_err("%s: timing out email\n", __func__); lws_dll2_remove(&e->list); n = lws_snprintf(buf, sizeof(buf), "0 Timed out retrying send"); @@ -78,21 +111,21 @@ again: /* is it time for his retry yet? */ if (e->last_try && - (time_t)lws_now_secs() - e->last_try < (time_t)c->i.retry_interval) { + (time_t)lws_now_secs() - e->last_try < (time_t)c->retry_interval) { /* no... send him to the tail */ lws_dll2_remove(&e->list); lws_dll2_add_tail(&e->list, &c->pending_owner); return; } - /* check if we have a connection to the server ongoing */ + /* ask the transport if we have a connection to the server ongoing */ - if (c->abs.state(c->abs_conn)) { + if (c->abs->at->state(c->abs->ati)) { /* * there's a connection, it could be still trying to connect * or established */ - c->abs.ask_for_writeable(c->abs_conn); + c->abs->at->ask_for_writeable(c->abs->ati); return; } @@ -101,7 +134,7 @@ again: lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING); - if (c->abs.client_conn(c->abs_conn, c->i.vh, c->i.abs_tokens)) { + if (c->abs->at->client_conn(c->abs)) { lwsl_err("%s: failed to connect\n", __func__); return; @@ -116,9 +149,9 @@ again: */ static int -lws_smtp_client_abs_accept(lws_abs_user_t *abs_priv) +lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api) { - lws_smtp_client_t *c = (lws_smtp_client_t *)abs_priv; + lws_smtp_client_t *c = (lws_smtp_client_t *)api; lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED); @@ -126,9 +159,9 @@ lws_smtp_client_abs_accept(lws_abs_user_t *abs_priv) } static int -lws_smtp_client_abs_rx(lws_abs_user_t *abs_priv, uint8_t *buf, size_t len) +lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len) { - lws_smtp_client_t *c = (lws_smtp_client_t *)abs_priv; + lws_smtp_client_t *c = (lws_smtp_client_t *)api; lws_smtp_email_t *e; lws_dll2_t *pd2; int n; @@ -151,7 +184,7 @@ lws_smtp_client_abs_rx(lws_abs_user_t *abs_priv, uint8_t *buf, size_t len) lws_dll2_remove(&e->list); lws_dll2_add_tail(&e->list, &c->pending_owner); lws_smtp_client_state_transition(c, LGSSMTP_IDLE); - lws_smtp_client_kick(c); + lws_smtp_client_kick_internal(c); return 0; } @@ -168,15 +201,15 @@ lws_smtp_client_abs_rx(lws_abs_user_t *abs_priv, uint8_t *buf, size_t len) } c->send_pending = 1; - c->abs.ask_for_writeable(c->abs_conn); + c->abs->at->ask_for_writeable(c->abs->ati); return 0; } static int -lws_smtp_client_abs_writeable(lws_abs_user_t *abs_priv, size_t budget) +lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget) { - lws_smtp_client_t *c = (lws_smtp_client_t *)abs_priv; + lws_smtp_client_t *c = (lws_smtp_client_t *)api; char b[256 + LWS_PRE], *p = b + LWS_PRE; lws_smtp_email_t *e; lws_dll2_t *pd2; @@ -200,7 +233,7 @@ lws_smtp_client_abs_writeable(lws_abs_user_t *abs_priv, size_t budget) switch (c->estate) { case LGSSMTP_CONNECTED: - n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->i.helo); + n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->helo); lws_smtp_client_state_transition(c, LGSSMTP_SENT_HELO); break; case LGSSMTP_SENT_HELO: @@ -234,27 +267,28 @@ lws_smtp_client_abs_writeable(lws_abs_user_t *abs_priv, size_t budget) } //puts(p); - c->abs.tx(c->abs_conn, (uint8_t *)p, n); + c->abs->at->tx(c->abs->ati, (uint8_t *)p, n); return 0; } static int -lws_smtp_client_abs_closed(lws_abs_user_t *abs_priv) +lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api) { - lws_smtp_client_t *c = (lws_smtp_client_t *)abs_priv; + lws_smtp_client_t *c = (lws_smtp_client_t *)api; - c->abs_conn = NULL; + if (c) + lws_smtp_client_state_transition(c, LGSSMTP_IDLE); return 0; } static int -lws_smtp_client_abs_heartbeat(lws_abs_user_t *abs_priv) +lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api) { - lws_smtp_client_t *c = (lws_smtp_client_t *)abs_priv; + lws_smtp_client_t *c = (lws_smtp_client_t *)api; - lws_smtp_client_kick(c); + lws_smtp_client_kick_internal(c); return 0; } @@ -302,11 +336,13 @@ lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len, } int -lws_smtp_client_add_email(lws_smtp_client_t *c, lws_smtp_email_t *e) +lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e) { - if (c->pending_owner.count > c->i.email_queue_max) { + lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api; + + if (c->pending_owner.count > c->email_queue_max) { lwsl_err("%s: email queue at limit of %d\n", __func__, - (int)c->i.email_queue_max); + (int)c->email_queue_max); return 1; } @@ -318,50 +354,53 @@ lws_smtp_client_add_email(lws_smtp_client_t *c, lws_smtp_email_t *e) lws_dll2_clear(&e->list); lws_dll2_add_tail(&e->list, &c->pending_owner); - lws_smtp_client_kick(c); + lws_smtp_client_kick_internal(c); return 0; } -lws_smtp_client_t * -lws_smtp_client_create(const lws_smtp_client_info_t *ci) +void +lws_smtp_client_kick(lws_abs_t *instance) { - lws_smtp_client_t *c; + lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api; - c = lws_zalloc(sizeof(*c), "email client"); - if (!c) - return NULL; + lws_smtp_client_kick_internal(c); +} +static int +lws_smtp_client_create(const lws_abs_t *ai) +{ + lws_smtp_client_t *c = (lws_smtp_client_t *)ai->api; + const lws_token_map_t *tm; - c->i = *ci; - c->abs = *ci->abs; + memset(c, 0, sizeof(*c)); - /* fill in the additional abstract callbacks we fulfil */ + c->abs = ai; - c->abs.accept = lws_smtp_client_abs_accept; - c->abs.rx = lws_smtp_client_abs_rx; - c->abs.writeable = lws_smtp_client_abs_writeable; - c->abs.closed = lws_smtp_client_abs_closed; - c->abs.heartbeat = lws_smtp_client_abs_heartbeat; + tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_V_HELO); + if (!tm) { + lwsl_err("%s: LTMI_PSMTP_V_HELO is required\n", __func__); - if (!c->i.email_queue_max) - c->i.email_queue_max = 8; - - if (!c->i.retry_interval) - c->i.retry_interval = 15 * 60; - - if (!c->i.delivery_timeout) - c->i.delivery_timeout = 12 * 60 * 60; - - c->abs_conn = c->abs.create(&c->abs, c); - if (!c->abs_conn) { - lws_free(c); - - return NULL; + return 1; } + c->helo = tm->u.value; + + c->email_queue_max = 8; + c->retry_interval = 15 * 60; + c->delivery_timeout = 12 * 60 * 60; + + tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_EMAIL_QUEUE_MAX); + if (tm) + c->email_queue_max = tm->u.lvalue; + tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_RETRY_INTERVAL); + if (tm) + c->retry_interval = tm->u.lvalue; + tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_DELIVERY_TIMEOUT); + if (tm) + c->delivery_timeout = tm->u.lvalue; lws_smtp_client_state_transition(c, LGSSMTP_IDLE); - return c; + return 0; } static int @@ -376,18 +415,37 @@ cleanup(struct lws_dll2 *d, void *user) return 0; } -void -lws_smtp_client_destroy(lws_smtp_client_t **c) +static void +lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c) { - if (!*c) + lws_smtp_client_t *c = (lws_smtp_client_t *)*_c; + + if (!c) return; - lws_dll2_foreach_safe(&(*c)->pending_owner, NULL, cleanup); + lws_dll2_foreach_safe(&c->pending_owner, NULL, cleanup); - if ((*c)->abs_conn) { - (*c)->abs.close((*c)->abs_conn); - (*c)->abs.destroy(&(*c)->abs_conn); - } + /* + * We don't free anything because the abstract layer combined our + * allocation with that of the instance, and it will free the whole + * thing after this. + */ - lws_free_set_NULL(*c); + *_c = NULL; } + +/* events the transport invokes (handled by abstract protocol) */ + +const lws_abs_protocol_t lws_abs_protocol_smtp = { + .name = "smtp", + .alloc = sizeof(lws_smtp_client_t), + + .create = lws_smtp_client_create, + .destroy = lws_smtp_client_destroy, + + .accept = lws_smtp_client_abs_accept, + .rx = lws_smtp_client_abs_rx, + .writeable = lws_smtp_client_abs_writeable, + .closed = lws_smtp_client_abs_closed, + .heartbeat = lws_smtp_client_abs_heartbeat, +}; diff --git a/lib/abstract/smtp/private.h b/lib/abstract/smtp/private.h deleted file mode 100644 index bbc51f85c..000000000 --- a/lib/abstract/smtp/private.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * libwebsockets lib/abstruct/smtp/private.h - * - * Copyright (C) 2019 Andy Green - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include "abstract/private.h" - -/** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */ -typedef enum lwsgs_smtp_states { - LGSSMTP_IDLE, /**< awaiting new email */ - LGSSMTP_CONNECTING, /**< opening tcp connection to MTA */ - LGSSMTP_CONNECTED, /**< tcp connection to MTA is connected */ - LGSSMTP_SENT_HELO, /**< sent the HELO */ - LGSSMTP_SENT_FROM, /**< sent FROM */ - LGSSMTP_SENT_TO, /**< sent TO */ - LGSSMTP_SENT_DATA, /**< sent DATA request */ - LGSSMTP_SENT_BODY, /**< sent the email body */ - LGSSMTP_SENT_QUIT, /**< sent the session quit */ -} lwsgs_smtp_states_t; - -/** struct lws_email - abstract context for performing SMTP operations */ -typedef struct lws_smtp_client { - struct lws_dll2_owner pending_owner; - - lws_smtp_client_info_t i; - lws_abstract_t abs; - - lws_abs_user_t *abs_conn; - - lwsgs_smtp_states_t estate; - time_t email_connect_started; - - unsigned char send_pending:1; -} lws_smtp_client_t; - diff --git a/lib/abstract/transports/raw-skt.c b/lib/abstract/transports/raw-skt.c index 634fbca54..9be6df098 100644 --- a/lib/abstract/transports/raw-skt.c +++ b/lib/abstract/transports/raw-skt.c @@ -1,5 +1,5 @@ /* - * libwebsockets lib/abstruct/transports/raw-skt.c + * libwebsockets lib/abstract/transports/raw-skt.c * * Copyright (C) 2019 Andy Green * @@ -22,16 +22,15 @@ #include "core/private.h" #include "abstract/private.h" -typedef struct lws_atrs_priv { - struct lws_abstract *abs; +typedef struct lws_abstxp_raw_skt_priv { + struct lws_abs *abs; struct lws *wsi; - void *user; lws_dll2_t same_abs_transport_list; uint8_t established:1; uint8_t connecting:1; -} lws_atrs_priv_t; +} abs_raw_skt_priv_t; struct vhd { lws_dll2_owner_t owner; @@ -40,10 +39,11 @@ struct vhd { static int heartbeat_cb(struct lws_dll2 *d, void *user) { - lws_atrs_priv_t *priv = lws_container_of(d, lws_atrs_priv_t, - same_abs_transport_list); - if (priv->abs->heartbeat) - priv->abs->heartbeat(priv->user); + abs_raw_skt_priv_t *priv = lws_container_of(d, abs_raw_skt_priv_t, + same_abs_transport_list); + + if (priv->abs->ap->heartbeat) + priv->abs->ap->heartbeat(priv->abs->api); return 0; } @@ -52,7 +52,7 @@ static int callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)user; + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)user; struct vhd *vhd = (struct vhd *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); @@ -89,8 +89,8 @@ callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason, lwsl_debug("LWS_CALLBACK_RAW_CONNECTED\n"); priv->connecting = 0; priv->established = 1; - if (priv->abs->accept) - priv->abs->accept(priv->user); + if (priv->abs->ap->accept) + priv->abs->ap->accept(priv->abs->api); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: @@ -105,19 +105,18 @@ callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason, lwsl_debug("LWS_CALLBACK_RAW_CLOSE\n"); priv->established = 0; priv->connecting = 0; - if (priv->abs && priv->abs->closed) - priv->abs->closed(priv->user); - lws_free(priv); + if (priv->abs && priv->abs->ap->closed) + priv->abs->ap->closed(priv->abs->api); lws_set_wsi_user(wsi, NULL); break; case LWS_CALLBACK_RAW_RX: lwsl_debug("LWS_CALLBACK_RAW_RX (%d)\n", (int)len); - return !!priv->abs->rx(priv->user, in, len); + return !!priv->abs->ap->rx(priv->abs->api, in, len); case LWS_CALLBACK_RAW_WRITEABLE: lwsl_debug("LWS_CALLBACK_RAW_WRITEABLE\n"); - priv->abs->writeable(priv->user, + priv->abs->ap->writeable(priv->abs->api, lws_get_peer_write_allowance(priv->wsi)); break; @@ -136,103 +135,10 @@ callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason, return 0; } -const struct lws_protocols protocol_abs_client_raw_skt = { - "lws-abs-cli-raw-skt", callback_abs_client_raw_skt, - 0, 1024, 1024, NULL, 0 -}; - static int -lws_atcrs_tx(lws_abs_user_t *abs_priv, uint8_t *buf, size_t len) +lws_atcrs_close(lws_abs_transport_inst_t *ati) { - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)abs_priv; - - if (!priv->wsi) { - lwsl_err("%s: NULL priv->wsi\n", __func__); - return 1; - } - - lwsl_debug("%s: priv %p, wsi %p, ro %p\n", __func__, - priv, priv->wsi, priv->wsi->role_ops); - - if (lws_write(priv->wsi, buf, len, LWS_WRITE_RAW) < 0) - priv->abs->close(priv->user); - - return 0; -} - -#if !defined(LWS_WITHOUT_CLIENT) -static int -lws_atcrs_client_conn(lws_abs_user_t *abs_priv, struct lws_vhost *vh, - const lws_token_map_t *token_map) -{ - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)abs_priv; - struct lws_client_connect_info i; - const lws_token_map_t *tm; - - if (priv->connecting) - return 0; - - if (priv->established) { - lws_set_timeout(priv->wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5); - - return 0; - } - - memset(&i, 0, sizeof(i)); - - /* address and port are passed-in using the abstract transport tokens */ - - tm = lws_abstract_get_token(token_map, LTMI_PEER_DNS_ADDRESS); - if (!tm) { - lwsl_notice("%s: raw_skt needs LTMI_PEER_DNS_ADDRESS\n", - __func__); - - return 1; - } - i.address = tm->u.value; - - tm = lws_abstract_get_token(token_map, LTMI_PEER_PORT); - if (!tm) { - lwsl_notice("%s: raw_skt needs LTMI_PEER_PORT\n", __func__); - - return 1; - } - i.port = tm->u.lvalue; - - /* optional */ - i.ssl_connection = 0; - tm = lws_abstract_get_token(token_map, LTMI_PEER_TLS_FLAGS); - if (tm) - i.ssl_connection = tm->u.lvalue; - - - lwsl_debug("%s: raw_skt priv %p connecting to %s:%u %p\n", - __func__, priv, i.address, i.port, vh->context); - - i.path = ""; - i.vhost = vh; - i.method = "RAW"; - i.userdata = priv; - i.host = i.address; - i.pwsi = &priv->wsi; - i.origin = i.address; - i.context = vh->context; - i.local_protocol_name = "lws-abs-cli-raw-skt"; - - priv->wsi = lws_client_connect_via_info(&i); - if (!priv->wsi) - return 1; - - priv->connecting = 1; - - return 0; -} -#endif - -static int -lws_atcrs_close(lws_abs_user_t *abs_priv) -{ - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)abs_priv; + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati; struct lws *wsi = priv->wsi; if (!priv->wsi) @@ -249,10 +155,103 @@ lws_atcrs_close(lws_abs_user_t *abs_priv) return 0; } + +const struct lws_protocols protocol_abs_client_raw_skt = { + "lws-abs-cli-raw-skt", callback_abs_client_raw_skt, + 0, 1024, 1024, NULL, 0 +}; + static int -lws_atcrs_ask_for_writeable(lws_abs_user_t *abs_priv) +lws_atcrs_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len) { - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)abs_priv; + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati; + + if (!priv->wsi) { + lwsl_err("%s: NULL priv->wsi\n", __func__); + return 1; + } + + lwsl_debug("%s: priv %p, wsi %p, ro %p\n", __func__, + priv, priv->wsi, priv->wsi->role_ops); + + if (lws_write(priv->wsi, buf, len, LWS_WRITE_RAW) < 0) + lws_atcrs_close(ati); + + return 0; +} + +#if !defined(LWS_WITHOUT_CLIENT) +static int +lws_atcrs_client_conn(const lws_abs_t *abs) +{ + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)abs->ati; + struct lws_client_connect_info i; + const lws_token_map_t *tm; + + if (priv->connecting) + return 0; + + if (priv->established) { + lws_set_timeout(priv->wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5); + + return 0; + } + + memset(&i, 0, sizeof(i)); + + /* address and port are passed-in using the abstract transport tokens */ + + tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_DNS_ADDRESS); + if (!tm) { + lwsl_notice("%s: raw_skt needs LTMI_PEER_V_DNS_ADDRESS\n", + __func__); + + return 1; + } + i.address = tm->u.value; + + tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_LV_PORT); + if (!tm) { + lwsl_notice("%s: raw_skt needs LTMI_PEER_LV_PORT\n", __func__); + + return 1; + } + i.port = tm->u.lvalue; + + /* optional */ + i.ssl_connection = 0; + tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_LV_TLS_FLAGS); + if (tm) + i.ssl_connection = tm->u.lvalue; + + + lwsl_debug("%s: raw_skt priv %p connecting to %s:%u %p\n", + __func__, priv, i.address, i.port, abs->vh->context); + + i.path = ""; + i.method = "RAW"; + i.vhost = abs->vh; + i.userdata = priv; + i.host = i.address; + i.pwsi = &priv->wsi; + i.origin = i.address; + i.context = abs->vh->context; + i.local_protocol_name = "lws-abs-cli-raw-skt"; + + priv->wsi = lws_client_connect_via_info(&i); + if (!priv->wsi) + return 1; + + priv->connecting = 1; + + return 0; +} +#endif + +static int +lws_atcrs_ask_for_writeable(lws_abs_transport_inst_t *ati) +{ + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati; if (!priv->wsi || !priv->established) return 1; @@ -262,32 +261,32 @@ lws_atcrs_ask_for_writeable(lws_abs_user_t *abs_priv) return 0; } -static lws_abs_user_t * -lws_atcrs_create(struct lws_abstract *abs, void *user) +static int +lws_atcrs_create(struct lws_abs *ai) { - lws_atrs_priv_t *p = lws_zalloc(sizeof(*p), __func__); + abs_raw_skt_priv_t *at = (abs_raw_skt_priv_t *)ai->ati; - if (!p) - return NULL; + memset(at, 0, sizeof(*at)); + at->abs = ai; - lwsl_debug("%s: created priv %p\n", __func__, p); - - p->abs = abs; - p->user = user; - - return (lws_abs_user_t *)p; + return 0; } static void -lws_atcrs_destroy(lws_abs_user_t **abs_priv) +lws_atcrs_destroy(lws_abs_transport_inst_t **pati) { - lws_free_set_NULL(*abs_priv); + /* + * We don't free anything because the abstract layer combined our + * allocation with that of the instance, and it will free the whole + * thing after this. + */ + *pati = NULL; } static int -lws_atcrs_set_timeout(lws_abs_user_t *d, int reason, int secs) +lws_atcrs_set_timeout(lws_abs_transport_inst_t *ati, int reason, int secs) { - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)d; + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati; lws_set_timeout(priv->wsi, reason, secs); @@ -295,9 +294,9 @@ lws_atcrs_set_timeout(lws_abs_user_t *d, int reason, int secs) } static int -lws_atcrs_state(lws_abs_user_t *abs_priv) +lws_atcrs_state(lws_abs_transport_inst_t *ati) { - lws_atrs_priv_t *priv = (lws_atrs_priv_t *)abs_priv; + abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati; if (!priv || !priv->wsi || (!priv->established && !priv->connecting)) return 0; @@ -305,29 +304,21 @@ lws_atcrs_state(lws_abs_user_t *abs_priv) return 1; } -lws_abstract_t lws_abstract_transport_cli_raw_skt = { - "raw-skt", - lws_atcrs_create, - lws_atcrs_destroy, +const lws_abs_transport_t lws_abs_transport_cli_raw_skt = { + .name = "raw_skt", + .alloc = sizeof(abs_raw_skt_priv_t), - lws_atcrs_tx, + .create = lws_atcrs_create, + .destroy = lws_atcrs_destroy, + + .tx = lws_atcrs_tx, #if defined(LWS_WITHOUT_CLIENT) - NULL, + .client_conn = NULL, #else - lws_atcrs_client_conn, + .client_conn = lws_atcrs_client_conn, #endif - lws_atcrs_close, - lws_atcrs_ask_for_writeable, - lws_atcrs_set_timeout, - lws_atcrs_state, - - /* - * remaining callbacks must be defined by abstract object and are - * called by this protocol handler - */ - - NULL, /* accept */ - NULL, /* rx */ - NULL, /* writeable */ - NULL /* closed */ + .close = lws_atcrs_close, + .ask_for_writeable = lws_atcrs_ask_for_writeable, + .set_timeout = lws_atcrs_set_timeout, + .state = lws_atcrs_state, }; diff --git a/lib/abstract/transports/unit-test.c b/lib/abstract/transports/unit-test.c new file mode 100644 index 000000000..7cd85cff8 --- /dev/null +++ b/lib/abstract/transports/unit-test.c @@ -0,0 +1,419 @@ +/* + * libwebsockets lib/abstract/transports/unit-test.c + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * An abstract transport that is useful for unit testing an abstract protocol. + * It doesn't actually connect to anything, but checks the protocol's response + * to various canned packets. + * + * Although it doesn't use any socket itself, it still needs to respect the + * event loop so it can reflect the associated behaviours correctly. So it + * creates a wsi for these purposes, which is a RAW FILE open on /dev/null. + */ + +#include "core/private.h" +#include "abstract/private.h" + +typedef struct lws_abstxp_unit_test_priv { + char note[128]; + struct lws_abs *abs; + + struct lws *wsi; + lws_expect_test_t *current_test; + lws_expect_t *expect; + lws_expect_disposition disposition; + int filefd; + + lws_dll2_t same_abs_transport_list; + + uint8_t established:1; + uint8_t connecting:1; +} abs_unit_test_priv_t; + +struct vhd { + lws_dll2_owner_t owner; +}; + +/* + * A definitive result has appeared for the current test + */ + +static lws_expect_disposition +lws_expect_dispose(abs_unit_test_priv_t *priv, lws_expect_disposition disp, + const char *note) +{ + assert(priv->disposition == LPE_CONTINUE); + + if (note) + lws_strncpy(priv->note, note, sizeof(priv->note)); + + priv->disposition = disp; + + lwsl_user("%s: %s: test %d: %s\n", priv->abs->ap->name, + priv->current_test->name, + (int)(priv->expect - priv->current_test->expect), + disp == LPE_SUCCEEDED ? "OK" : "FAIL"); + + return disp; +} + +/* + * start on the next step of the test + */ + +lws_expect_disposition +process_expect(abs_unit_test_priv_t *priv) +{ + assert(priv->disposition == LPE_CONTINUE); + + while (priv->expect->flags & LWS_AUT_EXPECT_RX) { + int f = priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE, + s = priv->abs->ap->rx(priv->abs->api, priv->expect->buffer, + priv->expect->len); + if (!!f != !!s) { + lwsl_notice("%s: expected rx return %d, got %d\n", + __func__, !!f, s); + + return lws_expect_dispose(priv, LPE_FAILED, + "rx unexpected return"); + } + + if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) + return lws_expect_dispose(priv, LPE_SUCCEEDED, NULL); + + priv->expect++; + } + + return LPE_CONTINUE; +} + +static int +heartbeat_cb(struct lws_dll2 *d, void *user) +{ + abs_unit_test_priv_t *priv = lws_container_of(d, abs_unit_test_priv_t, + same_abs_transport_list); + + if (priv->abs->ap->heartbeat) + priv->abs->ap->heartbeat(priv->abs->api); + + return 0; +} + +static int +callback_abs_client_unit_test(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)user; + struct vhd *vhd = (struct vhd *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), sizeof(struct vhd)); + if (!vhd) + return 1; + + lws_timed_callback_vh_protocol(lws_get_vhost(wsi), + lws_get_protocol(wsi), + LWS_CALLBACK_USER, 1); + break; + + case LWS_CALLBACK_USER: + /* + * This comes at 1Hz without a wsi context, so there is no + * valid priv. We need to track the live abstract objects that + * are using our abstract protocol, and pass the heartbeat + * through to the ones that care. + */ + if (!vhd) + break; + + lws_dll2_foreach_safe(&vhd->owner, NULL, heartbeat_cb); + + lws_timed_callback_vh_protocol(lws_get_vhost(wsi), + lws_get_protocol(wsi), + LWS_CALLBACK_USER, 1); + break; + + case LWS_CALLBACK_RAW_ADOPT_FILE: + lwsl_debug("LWS_CALLBACK_RAW_ADOPT_FILE\n"); + priv->connecting = 0; + priv->established = 1; + if (priv->abs->ap->accept) + priv->abs->ap->accept(priv->abs->api); + break; + + case LWS_CALLBACK_RAW_CLOSE_FILE: + if (!user) + break; + lwsl_debug("LWS_CALLBACK_RAW_CLOSE_FILE\n"); + priv->established = 0; + priv->connecting = 0; + if (priv->abs && priv->abs->ap->closed) + priv->abs->ap->closed(priv->abs->api); + if (priv->filefd != -1) + close(priv->filefd); + priv->filefd = -1; + lws_set_wsi_user(wsi, NULL); + break; + + case LWS_CALLBACK_RAW_WRITEABLE_FILE: + lwsl_debug("LWS_CALLBACK_RAW_WRITEABLE_FILE\n"); + priv->abs->ap->writeable(priv->abs->api, + lws_get_peer_write_allowance(priv->wsi)); + break; + + case LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL: + lws_dll2_add_tail(&priv->same_abs_transport_list, &vhd->owner); + break; + + case LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL: + lws_dll2_remove(&priv->same_abs_transport_list); + break; + + default: + break; + } + + return 0; +} + +const struct lws_protocols protocol_abs_client_unit_test = { + "lws-abs-cli-unit-test", callback_abs_client_unit_test, + 0, 1024, 1024, NULL, 0 +}; + +static int +lws_atcut_close(lws_abs_transport_inst_t *ati) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; + + lws_set_timeout(priv->wsi, 1, LWS_TO_KILL_SYNC); + + /* priv is destroyed in the CLOSE callback */ + + return 0; +} + +static int +lws_atcut_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; + + assert(priv->disposition == LPE_CONTINUE); + + if (!(priv->expect->flags & LWS_AUT_EXPECT_TX)) { + lwsl_notice("%s: unexpected tx\n", __func__); + lwsl_hexdump_notice(buf, len); + lws_expect_dispose(priv, LPE_FAILED, "unexpected tx"); + + return 1; + } + + if (len != priv->expect->len) { + lwsl_notice("%s: unexpected tx len %zu, expected %zu\n", + __func__, len, priv->expect->len); + lws_expect_dispose(priv, LPE_FAILED, "tx len mismatch"); + + return 1; + } + + if (memcmp(buf, priv->expect->buffer, len)) { + lwsl_notice("%s: tx mismatch (exp / actual)\n", __func__); + lwsl_hexdump_notice(priv->expect->buffer, len); + lwsl_hexdump_notice(buf, len); + lws_expect_dispose(priv, LPE_FAILED, "tx data mismatch"); + + return 1; + } + + if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) { + lws_expect_dispose(priv, LPE_SUCCEEDED, NULL); + + return 1; + } + + priv->expect++; + + return 0; +} + +#if !defined(LWS_WITHOUT_CLIENT) +static int +lws_atcut_client_conn(const lws_abs_t *abs) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)abs->ati; + const lws_token_map_t *tm; + lws_sock_file_fd_type u; + + /* + * we do this fresh for each test + */ + + if (priv->connecting || priv->established) + return 0; + + priv->filefd = lws_open("/dev/null", O_RDWR); + if (priv->filefd == -1) { + lwsl_err("%s: Unable to open /dev/null\n", __func__); + + return 1; + } + u.filefd = (lws_filefd_type)(long long)priv->filefd; + if (!lws_adopt_descriptor_vhost(priv->abs->vh, LWS_ADOPT_RAW_FILE_DESC, + u, "unit-test", NULL)) { + lwsl_err("Failed to adopt file descriptor\n"); + close(priv->filefd); + priv->filefd = -1; + + return 1; + } + + /* set up the test start pieces */ + + tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_TEST); + if (!tm) { + lwsl_notice("%s: unit_test needs LTMI_PEER_V_EXPECT_TEST\n", + __func__); + + return 1; + } + priv->current_test = (lws_expect_test_t *)tm->u.value; + priv->expect = priv->current_test->expect; + priv->disposition = LPE_CONTINUE; + priv->note[0] = '\0'; + + lwsl_notice("%s: %s: %s: start\n", __func__, abs->ap->name, + priv->current_test->name); + + process_expect(priv); + + priv->connecting = 1; + + return 0; +} +#endif + +static int +lws_atcut_ask_for_writeable(lws_abs_transport_inst_t *ati) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; + + if (!priv->established) + return 1; + + lws_callback_on_writable(priv->wsi); + + return 0; +} + +static int +lws_atcut_create(struct lws_abs *ai) +{ + abs_unit_test_priv_t *at = (abs_unit_test_priv_t *)ai->ati; + + memset(at, 0, sizeof(*at)); + at->abs = ai; + + return 0; +} + +static void +lws_atcut_destroy(lws_abs_transport_inst_t **pati) +{ + /* + * We don't free anything because the abstract layer combined our + * allocation with that of the instance, and it will free the whole + * thing after this. + */ + *pati = NULL; +} + +static int +lws_atcut_set_timeout(lws_abs_transport_inst_t *ati, int reason, int secs) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; + + lws_set_timeout(priv->wsi, reason, secs); + + return 0; +} + +static int +lws_atcut_state(lws_abs_transport_inst_t *ati) +{ + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; + + if (!priv || (!priv->established && !priv->connecting)) + return 0; + + return 1; +} + + +const lws_abs_transport_t lws_abs_transport_cli_unit_test = { + .name = "unit_test", + .alloc = sizeof(abs_unit_test_priv_t), + + .create = lws_atcut_create, + .destroy = lws_atcut_destroy, + + .tx = lws_atcut_tx, +#if defined(LWS_WITHOUT_CLIENT) + .client_conn = NULL, +#else + .client_conn = lws_atcut_client_conn, +#endif + .close = lws_atcut_close, + .ask_for_writeable = lws_atcut_ask_for_writeable, + .set_timeout = lws_atcut_set_timeout, + .state = lws_atcut_state, +}; + +/* + * This goes through the test array instantiating a new protocol + transport + * for each test and keeping track of the results + */ + +int +lws_abs_transport_unit_test_helper(lws_abs_t *abs) +{ + lws_abs_t *instance; + const lws_token_map_t *tm; + + tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_TEST_ARRAY); + if (!tm) { + lwsl_err("%s: LTMI_PEER_V_EXPECT_TEST_ARRAY is required\n", + __func__); + + return 1; + } + + //wh + + instance = lws_abs_bind_and_create_instance(abs); + if (!instance) { + lwsl_err("%s: failed to create SMTP client\n", __func__); + return 1; + } + + return 0; +} diff --git a/lib/core-net/private.h b/lib/core-net/private.h index 14c5c50a5..351e32af1 100644 --- a/lib/core-net/private.h +++ b/lib/core-net/private.h @@ -428,6 +428,8 @@ struct lws_vhost { const struct lws_protocol_vhost_options *headers; struct lws_dll *same_vh_protocol_heads; struct lws_vhost *no_listener_vhost_list; + struct lws_dll2_owner abstract_instances_owner; + #if !defined(LWS_NO_CLIENT) struct lws_dll dll_cli_active_conns_head; #endif @@ -1097,7 +1099,8 @@ int lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used, int buffered); -extern const struct lws_protocols protocol_abs_client_raw_skt; +extern const struct lws_protocols protocol_abs_client_raw_skt, + protocol_abs_client_unit_test; #ifdef __cplusplus }; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 4a54054f1..586336804 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -56,12 +56,17 @@ const struct lws_event_loop_ops *available_event_libs[] = { NULL }; +#if defined(LWS_WITH_ABSTRACT) const struct lws_protocols *available_abstract_protocols[] = { #if defined(LWS_ROLE_RAW) &protocol_abs_client_raw_skt, +#endif +#if defined(LWS_WITH_ABSTRACT) + &protocol_abs_client_unit_test, #endif NULL }; +#endif static const char * const mount_protocols[] = { "http://", @@ -433,7 +438,7 @@ lws_create_vhost(struct lws_context *context, struct lws_plugin *plugin = context->plugin_list; #endif struct lws_protocols *lwsp; - int m, f = !info->pvo, fx = 0, abs_pcol_count; + int m, f = !info->pvo, fx = 0, abs_pcol_count = 0; char buf[20]; #if !defined(LWS_WITHOUT_CLIENT) && defined(LWS_HAVE_GETENV) char *p; @@ -546,8 +551,9 @@ lws_create_vhost(struct lws_context *context, #if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS) fx = 1; #endif - +#if defined(LWS_WITH_ABSTRACT) abs_pcol_count = (int)LWS_ARRAY_SIZE(available_abstract_protocols) - 1; +#endif /* * give the vhost a unified list of protocols including: @@ -581,12 +587,13 @@ lws_create_vhost(struct lws_context *context, /* * 2: abstract protocols */ - +#if defined(LWS_WITH_ABSTRACT) for (n = 0; n < abs_pcol_count; n++) { memcpy(&lwsp[m++], available_abstract_protocols[n], sizeof(*lwsp)); vh->count_protocols++; } +#endif /* * 3: For compatibility, all protocols enabled on vhost if only @@ -972,6 +979,18 @@ out: lws_context_unlock(context); /* --------------------------- context { */ } +#if defined(LWS_WITH_ABSTRACT) +static int +destroy_ais(struct lws_dll2 *d, void *user) +{ + lws_abs_t *ai = lws_container_of(d, lws_abs_t, abstract_instances); + + lws_abs_destroy_instance(&ai); + + return 0; +} +#endif + void __lws_vhost_destroy2(struct lws_vhost *vh) { @@ -1112,6 +1131,14 @@ __lws_vhost_destroy2(struct lws_vhost *vh) if (vh->finalize) vh->finalize(vh, vh->finalize_arg); +#if defined(LWS_WITH_ABSTRACT) + /* + * abstract instances + */ + + lws_dll2_foreach_safe(&vh->abstract_instances_owner, NULL, destroy_ais); +#endif + lwsl_info(" %s: Freeing vhost %p\n", __func__, vh); memset(vh, 0, sizeof(*vh)); diff --git a/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt new file mode 100644 index 000000000..4c8e671ff --- /dev/null +++ b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-unit-tests-smtp-client) +set(SRCS main.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_WITH_SMTP 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/abstract/protocols/smtp-client/README.md b/minimal-examples/abstract/protocols/smtp-client/README.md new file mode 100644 index 000000000..a3b3d01ff --- /dev/null +++ b/minimal-examples/abstract/protocols/smtp-client/README.md @@ -0,0 +1,29 @@ +# lws api test smtp client + +Demonstrates how to send email through your local MTA + +## build + +Requires lws was built with `-DLWS_WITH_SMTP=1` at cmake. + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-r |Send the test email to this email address + + +``` + $ ./lws-api-test-smtp_client -r andy@warmcat.com +[2019/04/17 05:12:06:5293] USER: LWS API selftest: SMTP client +[2019/04/17 05:12:06:5635] NOTICE: LGSSMTP_IDLE: connecting to 127.0.0.1:25 +[2019/04/17 05:12:06:6238] NOTICE: email_sent_or_failed: sent OK +[2019/04/17 05:12:06:6394] USER: Completed: PASS + +``` + diff --git a/minimal-examples/abstract/protocols/smtp-client/main.c b/minimal-examples/abstract/protocols/smtp-client/main.c new file mode 100644 index 000000000..7ed859a06 --- /dev/null +++ b/minimal-examples/abstract/protocols/smtp-client/main.c @@ -0,0 +1,247 @@ +/* + * lws-unit-tests-smtp-client + * + * Written in 2010-2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + +#include + +static int interrupted, result = 1; +static const char *recip; + +/* + * from https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol + */ + +static lws_expect_t test_send1[] = { + { + "220 smtp.example.com ESMTP Postfix", + 34, LWS_AUT_EXPECT_RX + }, { + "HELO lws-test-client", + 20, LWS_AUT_EXPECT_TX + }, { + "250 smtp.example.com, I am glad to meet you", + 43, LWS_AUT_EXPECT_RX + }, { + "MAIL FROM:", + 31, LWS_AUT_EXPECT_TX + }, { + "250 Ok", + 6, LWS_AUT_EXPECT_RX + }, { + "RCPT TO:andy@warmcat.com", + 24, LWS_AUT_EXPECT_TX + }, { + "250 Ok", + 6, LWS_AUT_EXPECT_RX + }, { + "DATA", + 4, LWS_AUT_EXPECT_TX + }, { + "354 End data with .", + 35, LWS_AUT_EXPECT_RX + }, { + "From: noreply@example.com\n" + "To: andy@warmcat.com\n" + "Subject: Test email for lws smtp-client\n" + "\n" + "Hello this was an api test for lws smtp-client\n" + "\r\n.\r\n", + 27 + 21 + 39 + 1 + 46 + 5, LWS_AUT_EXPECT_TX + }, { + "250 Ok: queued as 12345", + 23, LWS_AUT_EXPECT_RX + }, { + "QUIT", + 4, LWS_AUT_EXPECT_TX + }, { + "221 Bye", + 7, LWS_AUT_EXPECT_RX | + LWS_AUT_EXPECT_LOCAL_CLOSE | + LWS_AUT_EXPECT_DO_REMOTE_CLOSE | + LWS_AUT_EXPECT_TEST_END + } +}; + +static lws_expect_test_t tests[] = { + { "sending", test_send1 }, + { } +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +static int +email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) +{ + /* you could examine email->data here */ + if (buf) + lwsl_notice("%s: %.*s\n", __func__, (int)len, (const char *)buf); + else + lwsl_notice("%s:\n", __func__); + + /* destroy any allocations in email */ + + free((char *)email->payload); + + result = 0; + interrupted = 1; + + return 0; +} + +/* + * The test helper calls this on the instance it created to prepare it for + * the test. + */ + +static int +smtp_test_instance_init(lws_abs_t *instance) +{ + lws_smtp_email_t email; + + /* attach an email to it */ + + memset(&email, 0, sizeof(email)); + email.data = NULL /* email specific user data */; + email.email_from = "noreply@warmcat.com"; + email.email_to = "andy@warmcat.com"; + email.payload = malloc(2048); + if (!email.payload) + return 1; + + lws_snprintf((char *)email.payload, 2048, + "From: noreply@example.com\n" + "To: %s\n" + "Subject: Test email for lws smtp-client\n" + "\n" + "Hello this was an api test for lws smtp-client\n" + "\r\n.\r\n", recip); + email.done = email_sent_or_failed; + + if (lws_smtp_client_add_email(instance, &email)) { + lwsl_err("%s: failed to add email\n", __func__); + return 1; + } + + return 0; +} + +/* + * We're going to bind to the raw-skt transport, so tell that what we want it + * to connect to + */ + +static const lws_token_map_t smtp_raw_skt_transport_tokens[] = { + { + .u = { .value = (const char *)tests }, + .name_index = LTMI_PEER_V_EXPECT_TEST_ARRAY, + }, { + } +}; + +static const lws_token_map_t smtp_protocol_tokens[] = { + { + .u = { .value = "lws-test-client" }, + .name_index = LTMI_PSMTP_V_HELO, + .init = smtp_test_instance_init, + }, { + } +}; + + +int main(int argc, const char **argv) +{ + int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; + struct lws_context *context; + struct lws_vhost *vh; + const char *p; + + /* the normal lws init */ + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + p = lws_cmdline_option(argc, argv, "-r"); + if (!p) { + lwsl_err("-r is required\n"); + return 1; + } + recip = p; + + lws_set_log_level(logs, NULL); + lwsl_user("LWS API selftest: SMTP client\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = CONTEXT_PORT_NO_LISTEN; + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + vh = lws_create_vhost(context, &info); + if (!vh) { + lwsl_err("Failed to create first vhost\n"); + goto bail1; + } + + /* create the smtp client */ + + memset(&abs, 0, sizeof(abs)); + abs.vh = vh; + + /* select the protocol and bind its tokens */ + + abs.ap = lws_abs_protocol_get_by_name("smtp"); + if (!abs.ap) + goto bail1; + abs.ap_tokens = smtp_protocol_tokens; + + /* select the transport and bind its tokens */ + + abs.at = lws_abs_transport_get_by_name("unit_tests"); + if (!abs.at) + goto bail1; + + /* + * The transport token we pass here to the test helper is the array + * of tests. The helper will iterate through it instantiating test + * connections with one test each. + */ + abs.at_tokens = smtp_raw_skt_transport_tokens; + + if (lws_abs_transport_unit_test_helper(&abs)) { + lwsl_err("%s: failed to create SMTP client\n", __func__); + goto bail1; + } + + + /* the usual lws event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + +bail: + +bail1: + lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); + + lws_context_destroy(context); + + return result; +} diff --git a/minimal-examples/api-tests/api-test-smtp_client/main.c b/minimal-examples/api-tests/api-test-smtp_client/main.c index 109350f92..d7a1d2d33 100644 --- a/minimal-examples/api-tests/api-test-smtp_client/main.c +++ b/minimal-examples/api-tests/api-test-smtp_client/main.c @@ -44,14 +44,24 @@ email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) * to connect to */ -static const lws_token_map_t smtp_abs_tokens[] = { -{ +static const lws_token_map_t smtp_raw_skt_transport_tokens[] = { + { .u = { .value = "127.0.0.1" }, - .name_index = LTMI_PEER_DNS_ADDRESS, -}, { - .u = { .lvalue = 25l }, - .name_index = LTMI_PEER_PORT, -}}; + .name_index = LTMI_PEER_V_DNS_ADDRESS, + }, { + .u = { .lvalue = 25 }, + .name_index = LTMI_PEER_LV_PORT, + }, { + } +}; + +static const lws_token_map_t smtp_protocol_tokens[] = { + { + .u = { .value = "lws-test-client" }, + .name_index = LTMI_PSMTP_V_HELO, + }, { + } +}; int main(int argc, const char **argv) @@ -59,8 +69,7 @@ int main(int argc, const char **argv) int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; struct lws_context_creation_info info; struct lws_context *context; - lws_smtp_client_info_t sci; - lws_smtp_client_t *smtpc; + lws_abs_t abs, *instance; lws_smtp_email_t email; struct lws_vhost *vh; const char *p; @@ -98,29 +107,39 @@ int main(int argc, const char **argv) goto bail1; } - /* create the smtp client */ + /* + * create an smtp client that's hooked up to real sockets + */ - memset(&sci, 0, sizeof(sci)); - sci.data = NULL; /* stmp client specific user data */ - sci.abs = lws_abstract_get_by_name("raw_skt"); - /* tell raw_skt transport what we want it to do */ - sci.abs_tokens = smtp_abs_tokens; - sci.vh = vh; + memset(&abs, 0, sizeof(abs)); + abs.vh = vh; - lws_strncpy(sci.helo, "lws-test-client", sizeof(sci.helo)); + /* select the protocol and bind its tokens */ - smtpc = lws_smtp_client_create(&sci); - if (!smtpc) { - lwsl_err("%s: failed to create SMTP client\n", __func__); + abs.ap = lws_abs_protocol_get_by_name("smtp"); + if (!abs.ap) + goto bail1; + + abs.ap_tokens = smtp_protocol_tokens; + + /* select the transport and bind its tokens */ + + abs.at = lws_abs_transport_get_by_name("raw_skt"); + if (!abs.at) + goto bail1; + + abs.at_tokens = smtp_raw_skt_transport_tokens; + + instance = lws_abs_bind_and_create_instance(&abs); + if (!instance) goto bail1; - } /* attach an email to it */ memset(&email, 0, sizeof(email)); email.data = NULL /* email specific user data */; - email.email_from = recip; - email.email_to = "andy@warmcat.com"; + email.email_from = "andy@warmcat.com"; + email.email_to = recip; email.payload = malloc(2048); if (!email.payload) { goto bail1; @@ -135,7 +154,7 @@ int main(int argc, const char **argv) "\r\n.\r\n", recip); email.done = email_sent_or_failed; - if (lws_smtp_client_add_email(smtpc, &email)) { + if (lws_smtp_client_add_email(instance, &email)) { lwsl_err("%s: failed to add email\n", __func__); goto bail; } @@ -146,7 +165,7 @@ int main(int argc, const char **argv) n = lws_service(context, 1000); bail: - lws_smtp_client_destroy(&smtpc); + bail1: lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); diff --git a/plugins/generic-sessions/private-lwsgs.h b/plugins/generic-sessions/private-lwsgs.h index c9d9bca44..841ed1aa3 100644 --- a/plugins/generic-sessions/private-lwsgs.h +++ b/plugins/generic-sessions/private-lwsgs.h @@ -62,8 +62,11 @@ struct lwsgs_user { }; struct per_vhost_data__gs { - lws_smtp_client_t *smtp_client; + lws_abs_t *smtp_client; struct lwsgs_user u; + lws_token_map_t transport_tokens[3]; + lws_token_map_t protocol_tokens[2]; + char helo[64], ip[64]; struct lws_context *context; char session_db[256]; char admin_user[32]; diff --git a/plugins/generic-sessions/protocol_generic_sessions.c b/plugins/generic-sessions/protocol_generic_sessions.c index ed2a82ead..3888820e5 100644 --- a/plugins/generic-sessions/protocol_generic_sessions.c +++ b/plugins/generic-sessions/protocol_generic_sessions.c @@ -136,10 +136,10 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, struct lws_session_info *sinfo; char s[LWSGS_EMAIL_CONTENT_SIZE]; unsigned char *p, *start, *end; - lws_smtp_client_info_t sci; const char *cp, *cp1; sqlite3_stmt *sm; lwsgw_hash sid; + lws_abs_t abs; int n; switch (reason) { @@ -157,10 +157,9 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, vhd->timeout_anon_absolute_secs = 1200; vhd->timeout_email_secs = 24 * 3600; - memset(&sci, 0, sizeof(sci)); - strcpy(sci.helo, "unconfigured.com"); - strcpy(sci.ip, "127.0.0.1"); + strcpy(vhd->helo, "unconfigured.com"); + strcpy(vhd->ip, "127.0.0.1"); strcpy(vhd->email_from, "noreply@unconfigured.com"); strcpy(vhd->email_title, "Registration Email from unconfigured"); vhd->urlroot[0] = '\0'; @@ -186,7 +185,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, lws_strncpy(vhd->email_from, pvo->value, sizeof(vhd->email_from)); if (!strcmp(pvo->name, "email-helo")) - lws_strncpy(sci.helo, pvo->value, sizeof(sci.helo)); + lws_strncpy(vhd->helo, pvo->value, sizeof(vhd->helo)); if (!strcmp(pvo->name, "email-template")) lws_strncpy(vhd->email_template, pvo->value, sizeof(vhd->email_template)); @@ -200,7 +199,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, lws_strncpy(vhd->email_confirm_url, pvo->value, sizeof(vhd->email_confirm_url)); if (!strcmp(pvo->name, "email-server-ip")) - lws_strncpy(sci.ip, pvo->value, sizeof(sci.ip)); + lws_strncpy(vhd->ip, pvo->value, sizeof(vhd->ip)); if (!strcmp(pvo->name, "timeout-idle-secs")) vhd->timeout_idle_secs = atoi(pvo->value); @@ -273,18 +272,38 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, return 1; } - sci.data = vhd; - sci.abs = lws_abstract_get_by_name("raw-skt"); - sci.vh = lws_get_vhost(wsi); + memset(&abs, 0, sizeof(abs)); + abs.vh = lws_get_vhost(wsi); - vhd->smtp_client = lws_smtp_client_create(&sci); - if (!vhd->smtp_client) { - lwsl_err("%s: failed to create SMTP client\n", __func__); + /* select the protocol and bind its tokens */ + + abs.ap = lws_abs_protocol_get_by_name("smtp"); + if (!abs.ap) + return 1; + + vhd->protocol_tokens[0].name_index = LTMI_PSMTP_V_HELO; + vhd->protocol_tokens[0].u.value = vhd->helo; + + abs.ap_tokens = vhd->protocol_tokens; + + /* select the transport and bind its tokens */ + + abs.at = lws_abs_transport_get_by_name("raw_skt"); + if (!abs.at) + return 1; + + vhd->transport_tokens[0].name_index = LTMI_PEER_V_DNS_ADDRESS; + vhd->transport_tokens[0].u.value = vhd->ip; + vhd->transport_tokens[1].name_index = LTMI_PEER_LV_PORT; + vhd->transport_tokens[1].u.lvalue = 25; + + abs.at_tokens = vhd->transport_tokens; + + vhd->smtp_client = lws_abs_bind_and_create_instance(&abs); + if (!vhd->smtp_client) return 1; - } lwsl_notice("%s: created SMTP client\n", __func__); - break; case LWS_CALLBACK_PROTOCOL_DESTROY: @@ -294,7 +313,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, vhd->pdb = NULL; } if (vhd->smtp_client) - lws_smtp_client_destroy(&vhd->smtp_client); + lws_abs_destroy_instance(&vhd->smtp_client); break; case LWS_CALLBACK_HTTP_WRITEABLE: