From a72b422be3a6f5e290a18f7069ef7554c34bca66 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Wed, 19 Jun 2019 19:10:14 +0100 Subject: [PATCH] abstract: add abstract transport tokens SMTP was improved to use the new abstract stuff a while ago, but it was only implemented with raw socket abstract transport, and a couple of 'api cheats' remained passing network information for the peer connection through the supposedly abstract apis. This patch adds a flexible generic token array to supply abstract transport-specific information through the abstract apis, removing the network information from the abstract connect() op. The SMTP minimal example is modified to use this new method to pass the network information. The abstract transport struct was opaque, but there are real uses to override it in user code, so this patch also makes it part of the public abi. --- doc-assets/abstract-overview.svg | 225 ++++++++++++++++++ include/libwebsockets/abstract/smtp.h | 4 +- include/libwebsockets/abstract/transports.h | 76 +++++- lib/abstract/README.md | 103 ++++++++ lib/abstract/abstract.c | 15 ++ lib/abstract/private.h | 46 +--- lib/abstract/smtp/smtp.c | 6 +- lib/abstract/transports/raw-skt.c | 38 ++- .../api-tests/api-test-smtp_client/main.c | 23 +- 9 files changed, 475 insertions(+), 61 deletions(-) create mode 100644 doc-assets/abstract-overview.svg create mode 100644 lib/abstract/README.md diff --git a/doc-assets/abstract-overview.svg b/doc-assets/abstract-overview.svg new file mode 100644 index 000000000..4da583c1b --- /dev/null +++ b/doc-assets/abstract-overview.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/libwebsockets/abstract/smtp.h b/include/libwebsockets/abstract/smtp.h index 85b912731..7ba5439fb 100644 --- a/include/libwebsockets/abstract/smtp.h +++ b/include/libwebsockets/abstract/smtp.h @@ -60,10 +60,12 @@ typedef struct lws_abstract lws_abstract_t; typedef struct lws_smtp_client_info { void *data; - char ip[32]; /**< Fill before init, eg, "127.0.0.1" */ 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; diff --git a/include/libwebsockets/abstract/transports.h b/include/libwebsockets/abstract/transports.h index 637521f2a..0d9dbf4d9 100644 --- a/include/libwebsockets/abstract/transports.h +++ b/include/libwebsockets/abstract/transports.h @@ -21,11 +21,83 @@ * included from libwebsockets.h */ -struct lws_abstract; -typedef struct lws_abstract lws_abstract_t; +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. + */ + +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 { + + const char *name; + + lws_abs_user_t * (*create)(struct lws_abstract *abs, void *user); + void (*destroy)(lws_abs_user_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 transport invokes (filled in by abstract object) */ + + 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_abstract_t; + 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); diff --git a/lib/abstract/README.md b/lib/abstract/README.md new file mode 100644 index 000000000..ae27df07c --- /dev/null +++ b/lib/abstract/README.md @@ -0,0 +1,103 @@ +# Abstract protocols and transports + +## Overview + +Until now protocol implementations in lws have been done directly +to the network-related apis inside lws. + +In an effort to separate out completely network implementation +details from protocol specification, lws now supports +"abstract protocols" and "abstract transports". + +![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. + +This makes it possible to confidently offer the same protocol on +completely different transports, eg, like serial, or to wire +up the protocol implementation to a test jig sending canned +test vectors and confirming the response at buffer level, without +any network. The abstract protocol itself has no relationship +to the transport at all and is completely unchanged by changes +to 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) + +### `lws_abstract_t` + +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. + +The transport defines about half of these and exports this +`lws_abstract_t *` via its name, it can be retreived using + +``` +LWS_VISIBLE LWS_EXTERN const lws_abstract_t * +lws_abstract_get_by_name(const char *name); +``` + +At the moment only "`raw-skt`" is defined as an lws built-in, athough +you can also create your own mock transport the same way for creating +test jigs. + +|transport op|meaning| +|---|---| +|`tx()`|transmit a buffer| +|`client_conn()`|start a connection to a peer| +|`close()`|request to close the connection to a peer| +|`ask_for_writeable()`|request a `writeable()` callback when tx can be used| +|`set_timeout()`|set a timeout that will close the connection if reached| +|`state()`|check if the connection is established and can carry traffic| + +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| +|`rx()`|The peer has sent us some payload| +|`writeable()`|The connection to the peer can take more tx| +|`closed()`|The connection to the peer has closed| +|`heartbeat()`|Called periodically even when no network events| + +These are called by the transport to inform the protocol of events +and traffic. + +### `lws_token_map_t` + +The abstract protocol has no idea about a network or network addresses +or ports or whatever... it may not even be hooked up to one. + +If the transport it is bound to wants things like that, they are passed +in using an array of `lws_token_map_t` at instantiation time. + +For example this is passed to the raw socket protocol in the smtp client +minimal example to control where it would connect to: + +``` +static const lws_token_map_t smtp_abs_tokens[] = { +{ + .u = { .value = "127.0.0.1" }, + .name_index = LTMI_PEER_DNS_ADDRESS, +}, { + .u = { .lvalue = 25l }, + .name_index = LTMI_PEER_PORT, +}}; +``` + diff --git a/lib/abstract/abstract.c b/lib/abstract/abstract.c index 2a602cf7b..52d7b5787 100644 --- a/lib/abstract/abstract.c +++ b/lib/abstract/abstract.c @@ -50,3 +50,18 @@ lws_abstract_get_by_name(const char *name) return NULL; } + +const lws_token_map_t * +lws_abstract_get_token(const lws_token_map_t *token_map, short name_index) +{ + if (!token_map) + return NULL; + + do { + if (token_map->name_index == name_index) + return token_map; + token_map++; + } while (token_map->name_index); + + return NULL; +} diff --git a/lib/abstract/private.h b/lib/abstract/private.h index 4962268d0..a9b1ca2de 100644 --- a/lib/abstract/private.h +++ b/lib/abstract/private.h @@ -19,50 +19,6 @@ * MA 02110-1301 USA */ -struct lws_abstract; - -typedef void lws_abs_user_t; -typedef void lws_abs_t; - -/* - * 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. - */ - -typedef struct lws_abstract { - - const char *name; - - lws_abs_user_t * (*create)(struct lws_abstract *abs, void *user); - void (*destroy)(lws_abs_user_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 char *ip, uint16_t port, int tls_flags); - 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 transport invokes (filled in by abstract object) */ - - 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_abstract_t; + diff --git a/lib/abstract/smtp/smtp.c b/lib/abstract/smtp/smtp.c index 988700f8c..454d9afc3 100644 --- a/lib/abstract/smtp/smtp.c +++ b/lib/abstract/smtp/smtp.c @@ -100,11 +100,9 @@ again: /* there's no existing connection */ lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING); - lwsl_notice("LGSSMTP_IDLE: connecting to %s:25\n", c->i.ip); - if (c->abs.client_conn(c->abs_conn, c->i.vh, c->i.ip, 25, 0)) { - lwsl_err("%s: failed to connect to %s:25\n", - __func__, c->i.ip); + if (c->abs.client_conn(c->abs_conn, c->i.vh, c->i.abs_tokens)) { + lwsl_err("%s: failed to connect\n", __func__); return; } diff --git a/lib/abstract/transports/raw-skt.c b/lib/abstract/transports/raw-skt.c index 739b7a0d0..634fbca54 100644 --- a/lib/abstract/transports/raw-skt.c +++ b/lib/abstract/transports/raw-skt.c @@ -163,10 +163,11 @@ lws_atcrs_tx(lws_abs_user_t *abs_priv, uint8_t *buf, size_t len) #if !defined(LWS_WITHOUT_CLIENT) static int lws_atcrs_client_conn(lws_abs_user_t *abs_priv, struct lws_vhost *vh, - const char *ip, uint16_t port, int tls_flags) + 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; @@ -177,22 +178,45 @@ lws_atcrs_client_conn(lws_abs_user_t *abs_priv, struct lws_vhost *vh, return 0; } - lwsl_debug("%s: priv %p connecting to %s:%u %p\n", __func__, priv, - ip, port, vh->context); - 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.port = port; - i.address = ip; i.method = "RAW"; i.userdata = priv; i.host = i.address; i.pwsi = &priv->wsi; i.origin = i.address; i.context = vh->context; - i.ssl_connection = tls_flags; i.local_protocol_name = "lws-abs-cli-raw-skt"; priv->wsi = lws_client_connect_via_info(&i); 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 4ba79f826..109350f92 100644 --- a/minimal-examples/api-tests/api-test-smtp_client/main.c +++ b/minimal-examples/api-tests/api-test-smtp_client/main.c @@ -9,6 +9,8 @@ #include +#include + static int interrupted, result = 1; static const char *recip; @@ -37,6 +39,21 @@ email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) 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_abs_tokens[] = { +{ + .u = { .value = "127.0.0.1" }, + .name_index = LTMI_PEER_DNS_ADDRESS, +}, { + .u = { .lvalue = 25l }, + .name_index = LTMI_PEER_PORT, +}}; + + int main(int argc, const char **argv) { int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; @@ -84,10 +101,12 @@ int main(int argc, const char **argv) /* create the smtp client */ memset(&sci, 0, sizeof(sci)); - sci.data = NULL /* stmp client specific user data */; + 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; - lws_strncpy(sci.ip, "127.0.0.1", sizeof(sci.ip)); + lws_strncpy(sci.helo, "lws-test-client", sizeof(sci.helo)); smtpc = lws_smtp_client_create(&sci);