Update Files

This commit is contained in:
2025-01-22 17:22:38 +01:00
parent 89b9349629
commit 4c5e729485
5132 changed files with 1195369 additions and 0 deletions

View File

@ -0,0 +1,27 @@
include_directories(../../../core
../../../core-net
../../../plat/unix
../../../plat/freertos
../../../tls
../../../event-libs
../../../roles
../../../roles/http
../../../roles/h1
../../../roles/h2
../../../roles/ws
../../../roles/mqtt
../../../roles/raw
../../../system
../../../system/smd
../../../system/fault-injection
../../../system/metrics
${LIBUV_INCLUDE_DIRS}
)
foreach(libpath ${LWS_LIB_BUILD_INC_PATHS})
include_directories(${libpath})
endforeach()

View File

@ -0,0 +1,89 @@
# SSPC client support
## Full vs LWS_ONLY_SSPC lws builds
SSPC (Secure Stream Proxy Client) apis are built into libwebsockets as you would
expect if built with `LWS_WITH_SECURE_STREAMS_PROXY_API`. These are the client
SS api implementation, the serialization and deserialization of SS protocol, and
generic transport interface.
You can also choose to build lws so it ONLY contains the SSPC pieces suitable
for the client side, with the libwebsockets build option `LWS_ONLY_SSPC`.
![lws-sspc build](../../../../doc-assets/lws-sspc-1.png)
Identical sources are used in both cases, but with `LWS_ONLY_SSPC` only the
parts related to mux and SSPC are included, coming to less than 25KB .text on
armv7 while providing the full proxied SS API. Also excluded are the normal
`lws_context` and event loop facilities, the provided apis expect to work with
an existing event loop.
`LWS_ONLY_SSPC` facilitates using full libwebsockets SSPC client support on very
small devices that don't need the rest of lws. Such devices do not need to have
tls, an IP stack or any network, yet can use full Secure Streams capabilities
via the proxy (eg, h2, including stream binding, or ws over tls).
![lws-sspc build](../../../../doc-assets/lws-sspc-2.png)
`LWS_ONLY_SSPC` does not need a normal `struct lws_context` nor the lws event
loop or other apis. To maintain compatibility with the SS apis the user code
provides a simple, static stub lws_context with a couple of members and no
creation or destruction.
You can find a minimal example that builds with `LWS_ONLY_SSPC` in
at `minimal-examples/secure-streams/minimal-secure-streams-custom-client-transport`,
implementing full SS apis over a UART link, where the client does not link to
lws nor need any network stack, tls library or internet protocol implementation.
It expects to use two USB serial adapters in loopback, one for this example and
one for the related proxy, minimal-secure-streams-custom-proxy-transport.
These two examples need different build dirs (but use the same libwebsockets
source) since the client requires lws built with `LWS_ONLY_SSPC`.
## Communication layers
Different transports have quite different semantics for connectivity. Serialized
SS communication has three abstract layers:
- link: connectivity is established. The serial cable is plugged in, the
RF link is established, or the Posix Socket has connected.
- mux: On some transports, eg, UART, there is a single logical link that we
want to be able to carry multiple SS connections, so the transport must
provide a way to negotiate multiplexing over the link.
- Serialized SS: the SS activity serialized into a stream in both directions
How these look depend on the nature of the underlying transport, some examples:
|layer|RF|UART|Unix Domain Socket|
|---|---|---|---|
|link|managed at RF stack|detect by periodic PING/PONG|Socket Connection semantics|
|mux|extra framing|extra framing|not needed, one UDS client socket for each SS|
|Serialized SS|bytestream|bytestream|Datagram stream|
SSPC can directly hook up to an lws_transport_ops structure, this is used in the
case where multiplexing is handled by the transport like Unix Domain Socket link
to the proxy. In that case, each SS makes its own personal Unix Domain Socket
link to the proxy.
The lws_transport_mux support is designed to interpose between SSPC layter and
the lws_transport itself, to provide its own multiplexing layer. This is needed
in the case where there is just a reliable bytestream link to the proxy, and no
suitable channelization or link detection, for example a simple UART transport.
lws_transport_mux provides 250 mux channels over the transport, with link
detection by three-way PING handshakes at the mux layer.
## LWS_ONLY_SSPC imports
Four system integration imports are needed by the library.
|prototype|function|
|---|---|
|lws_usec_t **lws_now_usecs**(void)|get us-resolution monotonic time|
|void **__lws_logv**(lws_log_cx_t *ignore1, lws_log_prepend_cx_t ignore2, void *ignore3, int filter, const char *_fun, const char *format, va_list ap)|log emit|
|void **lws_sul_schedule**(struct lws_context_standalone *ctx, int tsi, lws_sorted_usec_list_t *sul, sul_cb_t _cb, lws_usec_t _us)|schedule sul callback|
|void **lws_sul_cancel**(lws_sorted_usec_list_t *sul)|Cancel scheduled callback|

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
* Client SSPC where the connectivity is implemented by a wsi
*/
#include <private-lib-core.h>
static int
lws_sss_transport_wsi_cb(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)lws_get_opaque_user_data(wsi);
size_t pktsize = wsi->a.context->max_http_header_data;
lws_ss_state_return_t r;
switch (reason) {
case LWS_CALLBACK_CONNECTING:
/*
* In our particular case, we want CCEs even inside the
* initial connect loop time
*/
wsi->client_suppress_CONNECTION_ERROR = 0;
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_warn("%s: CCE: %s\n", __func__,
in ? (const char *)in : "null");
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free
* any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
lws_set_opaque_user_data(wsi, NULL);
h->txp_path.ops_in->event_connect_disposition(h, 1);
break;
case LWS_CALLBACK_RAW_CONNECTED:
lwsl_user("%s: CONNECTED\n", __func__);
if (h->txp_path.ops_in->event_connect_disposition(h, 0))
return -1;
/*
* We create the dsh at the response to the initial tx, which
* will let us know the policy's max size for it... let's
* protect the connection with a promise to complete the
* SS serialization streamtype negotation within a short period,
* we will cancel this timeout when we have the proxy's ack
* of the streamtype serialization, eg, it exists in the proxy
* policy etc
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);
break;
case LWS_CALLBACK_RAW_CLOSE:
/*
* our ss proxy Unix Domain socket has closed...
*/
lwsl_sspc_info(h, "LWS_CALLBACK_RAW_CLOSE: proxy conn down, wsi %s",
lws_wsi_tag(wsi));
if (h) {
r = h->txp_path.ops_in->event_closed(h);
h->txp_path.priv_in = NULL;
if (r == LWSSSSRET_DESTROY_ME) {
lws_set_opaque_user_data(wsi, NULL);
lws_sspc_destroy(&h);
}
}
break;
case LWS_CALLBACK_RAW_RX:
/*
* ie, the proxy has sent us something
*/
if (!h || !h->txp_path.priv_in) {
lwsl_info("%s: rx when client ss destroyed\n", __func__);
return -1;
}
lwsl_sspc_info(h, "%s: RAW_RX: rx %d\n", __func__, (int)len);
if (!len) {
lwsl_sspc_notice(h, "RAW_RX: zero len");
return -1;
}
r = h->txp_path.ops_in->event_read((lws_transport_priv_t)h,
(const uint8_t *)in, len);
switch (r) {
default:
break;
case LWSSSSRET_DISCONNECT_ME:
lwsl_info("%s: proxlicent RX ended with DISCONNECT_ME\n",
__func__);
return -1;
case LWSSSSRET_DESTROY_ME:
lwsl_info("%s: proxlicent RX ended with DESTROY_ME\n",
__func__);
lws_set_opaque_user_data(wsi, NULL);
lws_sspc_destroy(&h);
return -1;
}
if (h->state == LPCSCLI_LOCAL_CONNECTED ||
h->state == LPCSCLI_ONWARD_CONNECT)
lws_set_timeout(wsi, 0, 0);
break;
case LWS_CALLBACK_RAW_WRITEABLE:
/*
* We can transmit something to the proxy...
*/
if (!h)
break;
lwsl_sspc_debug(h, "WRITEABLE %s, state %d",
wsi->lc.gutag, h->state);
if (h->txp_path.ops_in->event_can_write(h, pktsize))
return -1;
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
const struct lws_protocols lws_sspc_protocols[] = {
{
"ssproxy-protocol",
lws_sss_transport_wsi_cb,
0,
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
/*
* lws_sss_transport ops for wsi transport
*/
static int
lws_sss_transport_wsi_retry_connect(lws_txp_path_client_t *path, lws_sspc_handle_t *h)
{
struct lws_client_connect_info i;
/*
* We may have started up before the system proxy, so be prepared with
* a sul to retry at 1Hz
*/
memset(&i, 0, sizeof i);
i.context = h->context;
if (h->context->ss_proxy_port) { /* tcp */
i.address = h->context->ss_proxy_address;
i.port = h->context->ss_proxy_port;
i.iface = h->context->ss_proxy_bind;
} else {
if (h->context->ss_proxy_bind)
i.address = h->context->ss_proxy_bind;
else
#if defined(__linux__)
i.address = "+@proxy.ss.lws";
#else
i.address = "+/tmp/proxy.ss.lws";
#endif
}
i.host = i.address;
i.origin = i.address;
i.method = "RAW";
i.protocol = lws_sspc_protocols[0].name;
i.local_protocol_name = lws_sspc_protocols[0].name;
i.path = "";
i.pwsi = (struct lws **)&h->txp_path.priv_onw;
i.opaque_user_data = (void *)h;
i.ssl_connection = LCCSCF_SECSTREAM_PROXY_LINK;
lws_metrics_caliper_bind(h->cal_txn, h->context->mt_ss_cliprox_conn);
#if defined(LWS_WITH_SYS_METRICS)
lws_metrics_tag_add(&h->cal_txn.mtags_owner, "ss", h->ssi.streamtype);
#endif
/* this wsi is the link to the proxy */
if (!lws_client_connect_via_info(&i)) {
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
return 1; /* going to need to retry */
}
lwsl_sspc_notice(h, "%s", ((struct lws *)(h->txp_path.priv_onw))->lc.gutag);
return 0; /* in progress */
}
static void
lws_sss_transport_wsi_req_write(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (wsi)
lws_callback_on_writable(wsi);
}
static int
lws_sss_transport_wsi_write(lws_transport_priv_t priv, uint8_t *buf, size_t len)
{
struct lws *wsi = (struct lws *)priv;
if (lws_write(wsi, buf, len, LWS_WRITE_RAW) != (ssize_t)len) {
lwsl_wsi_notice(wsi, "failed");
return -1;
}
return 0;
}
static void
lws_sss_transport_wsi_close(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (!wsi)
return;
lws_set_opaque_user_data(wsi, NULL);
lws_wsi_close(wsi, LWS_TO_KILL_ASYNC);
}
static void
lws_sss_transport_wsi_stream_up(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
}
const lws_transport_client_ops_t txp_ops_sspc_wsi = {
.name = "txp_sspc_wsi",
.event_retry_connect = lws_sss_transport_wsi_retry_connect,
.req_write = lws_sss_transport_wsi_req_write,
._write = lws_sss_transport_wsi_write,
._close = lws_sss_transport_wsi_close,
.event_stream_up = lws_sss_transport_wsi_stream_up,
.dsh_splitat = 1300,
};

View File

@ -0,0 +1,515 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* These are helpers used by the transport implementation. They contain the
* generic sspc actions to handle events that happen at the transport.
*/
#include <private-lib-core.h>
#if defined(STANDALONE)
#define lws_context lws_context_standalone
void
lws_ser_wu16be(uint8_t *b, uint16_t u)
{
*b++ = (uint8_t)(u >> 8);
*b = (uint8_t)u;
}
void
lws_ser_wu32be(uint8_t *b, uint32_t u32)
{
*b++ = (uint8_t)(u32 >> 24);
*b++ = (uint8_t)(u32 >> 16);
*b++ = (uint8_t)(u32 >> 8);
*b = (uint8_t)u32;
}
void
lws_ser_wu64be(uint8_t *b, uint64_t u64)
{
lws_ser_wu32be(b, (uint32_t)(u64 >> 32));
lws_ser_wu32be(b + 4, (uint32_t)u64);
}
#undef lws_malloc
#define lws_malloc(a, b) malloc(a)
#undef lws_free
#define lws_free(a) free(a)
#endif
static size_t
lws_sspc_serialize_metadata(lws_sspc_handle_t *h, lws_sspc_metadata_t *md,
uint8_t *p, uint8_t *end)
{
size_t n, txc;
if (md->name[0] == '\0') {
lwsl_sspc_info(h, "sending tx credit update %d",
md->tx_cr_adjust);
p[0] = LWSSS_SER_TXPRE_TXCR_UPDATE;
lws_ser_wu16be(&p[1], 4);
lws_ser_wu32be(&p[3], (uint32_t)md->tx_cr_adjust);
n = 7;
} else {
lwsl_sspc_info(h, "sending metadata");
p[0] = LWSSS_SER_TXPRE_METADATA;
txc = strlen(md->name);
n = txc + 1 + md->len;
if (n > 0xffff)
/* we can't serialize this metadata in 16b length */
return 0;
if (n > lws_ptr_diff_size_t(end, &p[4]))
/* we don't have space for this metadata */
return 0;
lws_ser_wu16be(&p[1], (uint16_t)n);
p[3] = (uint8_t)txc;
memcpy(&p[4], md->name, (unsigned int)txc);
memcpy(&p[4 + txc], &md[1], md->len);
n = 4 + txc + md->len;
}
lws_dll2_remove(&md->list);
lws_free(md);
return n;
}
/*
* An attempt to establish a link to the SS proxy has failed
*/
lws_ss_state_return_t
lws_sspc_txp_connect_disposition(lws_sspc_handle_t *h, int disposition)
{
lws_ss_state_return_t r;
uint64_t i;
if (!disposition) {
if (!h
#if !defined(STANDALONE)
|| lws_fi(&h->fic, "sspc_fail_on_linkup")
#endif
)
return 1;
lwsl_sspc_info(h, "CONNECTED (%s), %s", h->ssi.streamtype, h->txp_path.ops_onw->name);
h->state = LPCSCLI_SENDING_INITIAL_TX;
h->us_start_upstream = 0;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
h->txp_path.priv_onw = NULL;
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
if (!h->ssi.state)
return LWSSSSRET_OK;
i = (uint64_t)(lws_now_usecs() - h->us_start_upstream) / LWS_US_PER_MS;
if (i > 0xffffffffull)
i = 0xffffffffull;
r = h->ssi.state(lws_sspc_to_user_object(h), NULL,
LWSSSCS_UPSTREAM_LINK_RETRY, (uint32_t)i);
if (r == LWSSSSRET_DESTROY_ME)
lws_sspc_destroy(&h);
return LWSSSSRET_OK;
}
void
lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul)
{
lws_sspc_handle_t *h = lws_container_of(sul, lws_sspc_handle_t,
sul_retry);
if (h->txp_path.ops_onw->event_retry_connect(&h->txp_path, h))
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
}
/*
* The transport connection has closed
*/
lws_ss_state_return_t
lws_sspc_txp_event_closed(lws_transport_priv_t priv)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)priv;
lws_ss_state_return_t r = LWSSSSRET_OK;
if (!h) {
lwsl_sspc_info(h, "No sspc on client proxy link close");
return LWSSSSRET_OK;
}
h->parser.ps = RPAR_TYPE;
lws_dsh_empty(h->dsh);
h->txp_path.priv_onw = NULL;
h->conn_req_state = LWSSSPC_ONW_NONE;
if (h->ss_dangling_connected && h->ssi.state) {
lwsl_sspc_notice(h, "setting _DISCONNECTED");
h->ss_dangling_connected = 0;
h->prev_ss_state = LWSSSCS_DISCONNECTED;
r = h->ssi.state(ss_to_userobj(h), NULL,
LWSSSCS_DISCONNECTED, 0);
}
if (r != LWSSSSRET_DESTROY_ME)
/*
* schedule a reconnect in 1s
*/
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
return r;
}
/*
* We received rx from the proxy... caller must do destroy on DESTROY_ME
*/
lws_ss_state_return_t
lws_sspc_txp_rx_from_proxy(lws_transport_priv_t txp_priv, const uint8_t *in,
size_t len)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)txp_priv;
void *m = (void *)((uint8_t *)(h + 1));
assert(h);
#if !defined(STANDALONE)
if (lws_fi(&h->fic, "sspc_fake_rxparse_disconnect_me"))
return LWSSSSRET_DISCONNECT_ME;
if (lws_fi(&h->fic, "sspc_fake_rxparse_destroy_me"))
return LWSSSSRET_DESTROY_ME;
#endif
return lws_sspc_deserialize_parse(h, in, len, (lws_ss_handle_t **)m);
}
lws_ss_state_return_t
lws_sspc_txp_tx(lws_sspc_handle_t *h, size_t metadata_limit)
{
uint8_t *pkt = NULL, *p = NULL, *end = NULL;
void *m = (void *)((uint8_t *)(h + 1));
lws_ss_state_return_t r;
uint8_t _s[64 + LWS_PRE], *s = _s + LWS_PRE, *cp = s;
size_t txl, len;
lws_usec_t us;
int flags;
/*
* Management of ss timeout can happen any time and doesn't
* depend on wsi existence or state
*/
if (h->pending_timeout_update) {
cp = s;
*s = LWSSS_SER_TXPRE_TIMEOUT_UPDATE;
*(s + 1) = 0;
*(s + 2) = 4;
/*
* 0: use policy timeout value
* 0xffffffff: cancel the timeout
*/
lws_ser_wu32be(s + 3, h->timeout_ms);
/* in case anything else to write */
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
h->pending_timeout_update = 0;
txl = 7;
goto do_write;
}
*(s + 1) = 0;
/*
* This is the state of the link that connects us to the onward
* proxy
*/
switch (h->state) {
case LPCSCLI_SENDING_INITIAL_TX:
/*
* We are negotating the opening of a particular
* streamtype
*/
// lwsl_sspc_notice(h, "LPCSCLI_SENDING_INITIAL_TX");
txl = strlen(h->ssi.streamtype) + 1 + 4 + 4;
cp = s;
*s = LWSSS_SER_TXPRE_STREAMTYPE;
lws_ser_wu16be(s + 1, (uint16_t)txl);
/* SSSv1: add protocol version byte (initially 1) */
*(s + 3) = (uint8_t)LWS_SSS_CLIENT_PROTOCOL_VERSION;
#if defined(WIN32) || defined(LWS_PLAT_BAREMETAL)
lws_ser_wu32be(s + 4, (uint32_t)0);
#else
lws_ser_wu32be(s + 4, (uint32_t)getpid());
#endif
lws_ser_wu32be(s + 8, (uint32_t)h->txc.peer_tx_cr_est);
lws_strncpy((char *)(s + 12), h->ssi.streamtype,
(sizeof(_s) - LWS_PRE) - 12);
txl += 3;
h->state = LPCSCLI_WAITING_CREATE_RESULT;
goto do_write;
case LPCSCLI_LOCAL_CONNECTED:
// lwsl_sspc_notice(h, "LPCSCLI_LOCAL_CONNECTED");
/*
* Do we need to prioritize sending any metadata
* changes?
*/
if (h->metadata_owner.count) {
lws_sspc_metadata_t *md = lws_container_of(
lws_dll2_get_tail(&h->metadata_owner),
lws_sspc_metadata_t, list);
size_t n;
pkt = lws_malloc(metadata_limit + LWS_PRE, __func__);
if (!pkt)
goto hangup;
cp = p = pkt + LWS_PRE;
end = p + metadata_limit;
n = lws_sspc_serialize_metadata(h, md, p, end);
if (!n)
goto metadata_hangup;
txl = (size_t)n;
lwsl_sspc_debug(h, "(local_conn) metadata");
goto req_write_and_issue;
}
if (h->pending_writeable_len) {
lwsl_sspc_debug(h, "(local_conn) PAYLOAD_LENGTH_HINT %u",
(unsigned int)h->writeable_len);
cp = s;
*s = LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT;
lws_ser_wu16be(s + 1, 4);
lws_ser_wu32be(s + 3, (uint32_t)h->writeable_len);
h->pending_writeable_len = 0;
txl = 7;
goto req_write_and_issue;
}
if (h->conn_req_state >= LWSSSPC_ONW_ONGOING) {
lwsl_sspc_info(h, "conn_req_state %d",
h->conn_req_state);
break;
}
lwsl_sspc_info(h, "(local_conn) onward connect");
h->conn_req_state = LWSSSPC_ONW_ONGOING;
cp = s;
*s = LWSSS_SER_TXPRE_ONWARD_CONNECT;
*(s + 1) = 0;
*(s + 2) = 0;
txl = 3;
goto do_write;
case LPCSCLI_OPERATIONAL:
/*
*
* - Do we need to prioritize sending any metadata
* changes? (includes txcr updates)
*
* - Do we need to forward a hint about the payload
* length?
*/
pkt = lws_malloc(metadata_limit + LWS_PRE, __func__);
if (!pkt)
goto hangup;
cp = p = pkt + LWS_PRE;
end = p + metadata_limit;
if (h->metadata_owner.count) {
lws_sspc_metadata_t *md = lws_container_of(
lws_dll2_get_tail(&h->metadata_owner),
lws_sspc_metadata_t, list);
txl = lws_sspc_serialize_metadata(h, md, p, end);
if (!txl)
goto metadata_hangup;
goto req_write_and_issue;
}
if (h->pending_writeable_len) {
lwsl_sspc_info(h, "PAYLOAD_LENGTH_HINT %u",
(unsigned int)h->writeable_len);
cp = s;
*s = LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT;
lws_ser_wu16be(s + 1, 4);
lws_ser_wu32be(s + 3, (uint32_t)h->writeable_len);
h->pending_writeable_len = 0;
txl = 7;
goto req_write_and_issue;
}
/* we can't write anything if we don't have credit */
if (!h->ignore_txc && h->txc.tx_cr <= 0)
lwsl_sspc_info(h, "WRITEABLE / OPERATIONAL:"
" lack credit (%d)",
(int)h->txc.tx_cr);
len = metadata_limit - LWS_PRE - 19;
flags = 0;
if (!h->ssi.tx) {
txl = 0;
goto do_write_nz;
}
r = h->ssi.tx(m, h->ord++, pkt + LWS_PRE + 19, &len, &flags);
switch (r) {
case LWSSSSRET_TX_DONT_SEND:
txl = 0;
goto do_write_nz;
case LWSSSSRET_DISCONNECT_ME:
case LWSSSSRET_DESTROY_ME:
lwsl_sspc_warn(h, "sspc tx DISCONNECT/DESTROY TBD");
break;
default:
break;
}
h->txc.tx_cr = h->txc.tx_cr - (int)len;
cp = p;
txl = len + 19;
us = lws_now_usecs();
p[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], (uint16_t)(len + 19 - 3));
lws_ser_wu32be(&p[3], (uint32_t)flags);
/* time spent here waiting to send this */
lws_ser_wu32be(&p[7], (uint32_t)(us - h->us_earliest_write_req));
/* ust that the client write happened */
lws_ser_wu64be(&p[11], (uint64_t)us);
h->us_earliest_write_req = 0;
if (flags & LWSSS_FLAG_EOM)
if (h->rsidx + 1 < (int)LWS_ARRAY_SIZE(h->rideshare_ofs) &&
h->rideshare_ofs[h->rsidx + 1])
h->rsidx++;
goto do_write;
default:
break;
}
return LWSSSSRET_OK;
req_write_and_issue:
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
do_write_nz:
if (!txl) {
lws_free(pkt);
return LWSSSSRET_OK;
}
do_write:
if (
#if !defined(STANDALONE)
!lws_fi(&h->fic, "sspc_link_write_fail") &&
#endif
!h->txp_path.ops_onw->_write(h->txp_path.priv_onw, cp, txl)) {
if (pkt)
lws_free(pkt);
return LWSSSSRET_OK;
}
goto hangup;
metadata_hangup:
lwsl_sspc_err(h, "metadata too large");
hangup:
lws_free(pkt);
lwsl_sspc_warn(h, "hangup");
/* hang up on the proxy link */
return LWSSSSRET_DISCONNECT_ME;
}
void
lws_sspc_txp_lost_coherence(lws_transport_priv_t txp_priv)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)txp_priv;
lwsl_sspc_warn(h, "Lost Coherence");
h->conn_req_state = LWSSSPC_ONW_NONE;
/* pass thru to lower layer, eg, mux */
h->txp_path.ops_onw->lost_coherence(h->txp_path.priv_onw);
}
/*
* The actual client transports bind to this transport ops for "inside sspc".
* It's like this so we can transparently interpose the mux.
*
* Only the apis the transport needs to call on the inside need timplementing
* for this
*/
const lws_transport_client_ops_t lws_txp_inside_sspc = {
.name = "txp_inside_sspc",
.event_connect_disposition = lws_sspc_txp_connect_disposition,
.event_read = lws_sspc_txp_rx_from_proxy,
.event_can_write = lws_sspc_txp_tx,
.event_closed = lws_sspc_txp_event_closed,
.lost_coherence = lws_sspc_txp_lost_coherence,
};

View File

@ -0,0 +1,851 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
extern const uint32_t ss_state_txn_validity[17];
#if defined(STANDALONE)
#define lws_context lws_context_standalone
static const char *state_names[] = {
"(unset)",
"LWSSSCS_CREATING",
"LWSSSCS_DISCONNECTED",
"LWSSSCS_UNREACHABLE",
"LWSSSCS_AUTH_FAILED",
"LWSSSCS_CONNECTED",
"LWSSSCS_CONNECTING",
"LWSSSCS_DESTROYING",
"LWSSSCS_POLL",
"LWSSSCS_ALL_RETRIES_FAILED",
"LWSSSCS_QOS_ACK_REMOTE",
"LWSSSCS_QOS_NACK_REMOTE",
"LWSSSCS_QOS_ACK_LOCAL",
"LWSSSCS_QOS_NACK_LOCAL",
"LWSSSCS_TIMEOUT",
"LWSSSCS_SERVER_TXN",
"LWSSSCS_SERVER_UPGRADE",
"LWSSSCS_EVENT_WAIT_CANCELLED",
"LWSSSCS_UPSTREAM_LINK_RETRY",
};
const char *
lws_ss_state_name(int state)
{
if (state >= LWSSSCS_USER_BASE)
return "user state";
if (state >= (int)LWS_ARRAY_SIZE(state_names))
return "unknown";
return state_names[state];
}
const uint32_t ss_state_txn_validity[] = {
/* if we was last in this state... we can legally go to these states */
[0] = (1 << LWSSSCS_CREATING) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CREATING] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_SERVER_UPGRADE) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_DISCONNECTED] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_UNREACHABLE] = (1 << LWSSSCS_ALL_RETRIES_FAILED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_CONNECTING) |
/* win conn failure > retry > succ */
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_AUTH_FAILED] = (1 << LWSSSCS_ALL_RETRIES_FAILED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CONNECTED] = (1 << LWSSSCS_SERVER_UPGRADE) |
(1 << LWSSSCS_SERVER_TXN) |
(1 << LWSSSCS_AUTH_FAILED) |
(1 << LWSSSCS_QOS_ACK_REMOTE) |
(1 << LWSSSCS_QOS_NACK_REMOTE) |
(1 << LWSSSCS_QOS_ACK_LOCAL) |
(1 << LWSSSCS_QOS_NACK_LOCAL) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) | /* proxy retry */
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CONNECTING] = (1 << LWSSSCS_UNREACHABLE) |
(1 << LWSSSCS_AUTH_FAILED) |
(1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) | /* proxy retry */
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_DESTROYING] = 0,
[LWSSSCS_POLL] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_ALL_RETRIES_FAILED] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_ACK_REMOTE] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
#if defined(LWS_ROLE_MQTT)
(1 << LWSSSCS_QOS_ACK_REMOTE) |
#endif
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_NACK_REMOTE] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_ACK_LOCAL] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_NACK_LOCAL] = (1 << LWSSSCS_DESTROYING) |
(1 << LWSSSCS_TIMEOUT),
/* he can get the timeout at any point and take no action... */
[LWSSSCS_TIMEOUT] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_QOS_ACK_REMOTE) |
(1 << LWSSSCS_QOS_NACK_REMOTE) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_UNREACHABLE) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_SERVER_TXN] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_SERVER_UPGRADE] = (1 << LWSSSCS_SERVER_TXN) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_DESTROYING),
};
char *
lws_strncpy(char *dest, const char *src, size_t size)
{
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
#undef lws_malloc
#define lws_malloc(a, b) malloc(a)
#undef lws_free
#define lws_free(a) free(a)
extern void
__lws_logv(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj,
int filter, const char *_fun, const char *format, va_list ap);
void _lws_logv(int filter, const char *format, va_list ap)
{
__lws_logv(NULL, NULL, NULL, filter, NULL, format, ap);
}
void
_lws_log(int filter, const char *format, ...)
{
va_list ap;
va_start(ap, format);
_lws_logv(filter, format, ap);
va_end(ap);
}
void
_lws_log_cx(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj,
int filter, const char *_fun, const char *format, ...)
{
va_list ap;
va_start(ap, format);
__lws_logv(cx, prep, obj, filter, _fun, format, ap);
va_end(ap);
}
#endif
int
lws_ss_check_next_state_sspc(lws_sspc_handle_t *ss, uint8_t *prevstate,
lws_ss_constate_t cs)
{
if (cs >= LWSSSCS_USER_BASE || cs == LWSSSCS_EVENT_WAIT_CANCELLED)
/*
* we can't judge user or transient states, leave the old state
* and just wave them through
*/
return 0;
if (cs >= LWS_ARRAY_SIZE(ss_state_txn_validity)) {
/* we don't recognize this state as usable */
lwsl_sspc_err(ss, "bad new state %u", cs);
assert(0);
return 1;
}
if (*prevstate >= LWS_ARRAY_SIZE(ss_state_txn_validity)) {
/* existing state is broken */
lwsl_sspc_err(ss, "bad existing state %u",
(unsigned int)*prevstate);
assert(0);
return 1;
}
if (ss_state_txn_validity[*prevstate] & (1u << cs)) {
lwsl_sspc_notice(ss, "%s -> %s",
lws_ss_state_name((int)*prevstate),
lws_ss_state_name((int)cs));
/* this is explicitly allowed, update old state to new */
*prevstate = (uint8_t)cs;
return 0;
}
lwsl_sspc_err(ss, "transition from %s -> %s is illegal",
lws_ss_state_name((int)*prevstate),
lws_ss_state_name((int)cs));
assert(0);
return 1;
}
lws_ss_state_return_t
lws_sspc_event_helper(lws_sspc_handle_t *h, lws_ss_constate_t cs,
lws_ss_tx_ordinal_t flags)
{
lws_ss_state_return_t ret;
if (!h)
return LWSSSSRET_OK;
if (lws_ss_check_next_state_sspc(h, &h->prev_ss_state, cs))
return LWSSSSRET_DESTROY_ME;
if (!h->ssi.state)
return LWSSSSRET_OK;
h->h_in_svc = h;
ret = h->ssi.state((void *)((uint8_t *)(h + 1)), NULL, cs, flags);
h->h_in_svc = NULL;
return ret;
}
int
lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
void *opaque_user_data, lws_sspc_handle_t **ppss,
void *reserved, const char **ppayload_fmt)
{
lws_sspc_handle_t *h;
uint8_t *ua;
char *p;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(context, tsi);
#endif
/* allocate the handle (including ssi), the user alloc,
* and the streamname */
h = malloc(sizeof(lws_sspc_handle_t) + ssi->user_alloc +
strlen(ssi->streamtype) + 1);
if (!h)
return 1;
memset(h, 0, sizeof(*h));
#if !defined(STANDALONE)
h->lc.log_cx = context->log_cx;
#endif
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_FAULT_INJECTION)
h->fic.name = "sspc";
lws_xos_init(&h->fic.xos, lws_xos(&context->fic.xos));
if (ssi->fic.fi_owner.count)
lws_fi_import(&h->fic, &ssi->fic);
lws_fi_inherit_copy(&h->fic, &context->fic, "ss", ssi->streamtype);
if (lws_fi(&h->fic, "sspc_create_oom")) {
/*
* We have to do this a little later, so we can cleanly inherit
* the OOM pieces and drain the info fic
*/
lws_fi_destroy(&h->fic);
free(h);
return 1;
}
#endif
#if !defined(STANDALONE)
__lws_lc_tag(context, &context->lcg[LWSLCG_SSP_CLIENT], &h->lc,
ssi->streamtype);
#else
snprintf(h->lc.gutag, sizeof(h->lc.gutag), "[sspc|%s|%x]",
ssi->streamtype,
(unsigned int)(context->ssidx++));
#endif
h->txp_path = context->txp_cpath;
h->txp_path.ops_in = &lws_txp_inside_sspc;
h->txp_path.priv_in = (lws_transport_priv_t)h;
/* priv_onw filled in by onw transport */
lwsl_sspc_info(h, "txp path %s -> %s", h->txp_path.ops_in->name,
h->txp_path.ops_onw->name);
memcpy(&h->ssi, ssi, sizeof(*ssi));
ua = (uint8_t *)(h + 1);
memset(ua, 0, ssi->user_alloc);
p = (char *)ua + ssi->user_alloc;
memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1);
h->ssi.streamtype = (const char *)p;
h->context = context;
h->us_start_upstream = lws_now_usecs();
if (!ssi->manual_initial_tx_credit)
h->txc.peer_tx_cr_est = 500000000;
else
h->txc.peer_tx_cr_est = ssi->manual_initial_tx_credit;
#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_SYS_SMD)
if (!strcmp(ssi->streamtype, LWS_SMD_STREAMTYPENAME))
h->ignore_txc = 1;
#endif
lws_dll2_add_head(&h->client_list, &context->
#if !defined(STANDALONE)
pt[tsi].
#endif
ss_client_owner);
/* fill in the things the real api does for the caller */
*((void **)(ua + ssi->opaque_user_data_offset)) = opaque_user_data;
*((void **)(ua + ssi->handle_offset)) = h;
if (ppss)
*ppss = h;
/* try the actual connect */
lws_sspc_sul_retry_cb(&h->sul_retry);
return 0;
}
/* used on context destroy when iterating listed lws_ss on a pt */
int
lws_sspc_destroy_dll(struct lws_dll2 *d, void *user)
{
lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t,
client_list);
lws_sspc_destroy(&h);
return 0;
}
void
lws_sspc_rxmetadata_destroy(lws_sspc_handle_t *h)
{
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner_rx)) {
lws_sspc_metadata_t *md =
lws_container_of(d, lws_sspc_metadata_t, list);
lws_dll2_remove(&md->list);
lws_free(md);
} lws_end_foreach_dll_safe(d, d1);
}
void
lws_sspc_destroy(lws_sspc_handle_t **ph)
{
lws_sspc_handle_t *h;
if (!*ph)
return;
h = *ph;
if (h == h->h_in_svc) {
lwsl_err("%s: illegal destroy, return LWSSSSRET_DESTROY_ME instead\n",
__func__);
assert(0);
return;
}
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (h->destroying)
return;
h->destroying = 1;
/* if this caliper is still dangling at destroy, we failed */
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
if (h->ss_dangling_connected && h->ssi.state) {
lws_sspc_event_helper(h, LWSSSCS_DISCONNECTED, 0);
h->ss_dangling_connected = 0;
}
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_FAULT_INJECTION)
lws_fi_destroy(&h->fic);
#endif
lws_sul_cancel(&h->sul_retry);
lws_dll2_remove(&h->client_list);
if (h->dsh)
lws_dsh_destroy(&h->dsh);
h->txp_path.ops_onw->_close(h->txp_path.priv_onw);
/* clean out any pending metadata changes that didn't make it */
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&(*ph)->metadata_owner)) {
lws_sspc_metadata_t *md =
lws_container_of(d, lws_sspc_metadata_t, list);
lws_dll2_remove(&md->list);
lws_free(md);
} lws_end_foreach_dll_safe(d, d1);
lws_sspc_rxmetadata_destroy(h);
lws_sspc_event_helper(h, LWSSSCS_DESTROYING, 0);
*ph = NULL;
lws_sul_cancel(&h->sul_retry);
#if !defined(STANDALONE)
/* confirm no sul left scheduled in handle or user allocation object */
lws_sul_debug_zombies(h->context, h, sizeof(*h) + h->ssi.user_alloc,
__func__);
#endif
#if !defined(STANDALONE)
__lws_lc_untag(h->context, &h->lc);
#endif
free(h);
}
lws_ss_state_return_t
lws_sspc_request_tx(lws_sspc_handle_t *h)
{
if (!h || !h->txp_path.priv_onw)
return LWSSSSRET_OK;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
lwsl_info("%s: state %u, conn_req_state %u\n", __func__,
(unsigned int)h->state,
(unsigned int)h->conn_req_state);
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* Currently we fulfil the writeable part locally by just enabling POLLOUT on
* the UDS link, without serialization footprint, which is reasonable as far as
* it goes.
*
* But for the ..._len() variant, the expected payload length hint we are being
* told is something that must be serialized to the onward peer, since either
* that guy or someone upstream of him is the guy who will compose the framing
* with it that actually goes out.
*
* This information is needed at the upstream guy before we have sent any
* payload, eg, for http POST, he has to prepare the content-length in the
* headers, before any payload. So we have to issue a serialization of the
* length at this point.
*/
lws_ss_state_return_t
lws_sspc_request_tx_len(lws_sspc_handle_t *h, unsigned long len)
{
/*
* for client conns, they cannot even complete creation of the handle
* without the onwared connection to the proxy, it's not legal to start
* using it until it's operation and has the onward connection (and the
* link has called CREATED state)
*/
if (!h)
return LWSSSSRET_OK;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lwsl_sspc_notice(h, "setting writeable_len %u", (unsigned int)len);
h->writeable_len = len;
h->pending_writeable_len = 1;
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
/*
* We're going to use this up with serializing h->writeable_len... that
* will request again.
*/
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
lws_ss_state_return_t
lws_sspc_client_connect(struct lws_sspc_handle *h)
{
if (!h || h->state == LPCSCLI_OPERATIONAL)
return 0;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
assert(h->state == LPCSCLI_LOCAL_CONNECTED);
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return 0;
}
struct lws_context *
lws_sspc_get_context(struct lws_sspc_handle *h)
{
return h->context;
}
const char *
lws_sspc_rideshare(struct lws_sspc_handle *h)
{
/*
* ...the serialized RX rideshare name if any...
*/
if (h->parser.rideshare[0]) {
lwsl_sspc_info(h, "parser %s", h->parser.rideshare);
return h->parser.rideshare;
}
/*
* The tx rideshare index
*/
if (h->rideshare_list[0]) {
lwsl_sspc_info(h, "tx list %s",
&h->rideshare_list[h->rideshare_ofs[h->rsidx]]);
return &h->rideshare_list[h->rideshare_ofs[h->rsidx]];
}
/*
* ... otherwise default to our stream type name
*/
lwsl_sspc_info(h, "def %s\n", h->ssi.streamtype);
return h->ssi.streamtype;
}
static int
_lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
const void *value, size_t len, int tx_cr_adjust)
{
lws_sspc_metadata_t *md;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
/*
* Are we replacing a pending metadata of the same name? It's not
* efficient to do this but user code can do what it likes... let's
* optimize away the old one.
*
* Tx credit adjust always has name ""
*/
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner)) {
md = lws_container_of(d, lws_sspc_metadata_t, list);
if (!strcmp(name, md->name)) {
lws_dll2_remove(&md->list);
lws_free(md);
break;
}
} lws_end_foreach_dll_safe(d, d1);
/*
* We have to stash the metadata and pass it to the proxy
*/
#if !defined(STANDALONE)
if (lws_fi(&h->fic, "sspc_fail_metadata_set"))
md = NULL;
else
#endif
md = lws_malloc(sizeof(*md) + len, "set metadata");
if (!md) {
lwsl_sspc_err(h, "OOM");
return 1;
}
memset(md, 0, sizeof(*md));
md->tx_cr_adjust = tx_cr_adjust;
h->txc.peer_tx_cr_est += tx_cr_adjust;
lws_strncpy(md->name, name, sizeof(md->name));
md->len = len;
if (len)
memcpy(&md[1], value, len);
lws_dll2_add_tail(&md->list, &h->metadata_owner);
if (len) {
#if !defined(STANDALONE)
lwsl_sspc_info(h, "set metadata %s", name);
lwsl_hexdump_sspc_info(h, value, len);
#endif
} else
lwsl_sspc_info(h, "serializing tx cr adj %d",
(int)tx_cr_adjust);
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return 0;
}
void
lws_sspc_server_ack(struct lws_sspc_handle *h, int nack)
{
//h->txn_resp = nack;
//h->txn_resp_set = 1;
}
int
lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
const void *value, size_t len)
{
return _lws_sspc_set_metadata(h, name, value, len, 0);
}
int
lws_sspc_get_metadata(struct lws_sspc_handle *h, const char *name,
const void **value, size_t *len)
{
lws_sspc_metadata_t *md;
/*
* client side does not have access to policy
* and any metadata are new to it each time,
* we allocate them, removing any existing with
* the same name first
*/
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner_rx)) {
md = lws_container_of(d,
lws_sspc_metadata_t, list);
if (!strcmp(md->name, name)) {
*len = md->len;
*value = &md[1];
return 0;
}
} lws_end_foreach_dll_safe(d, d1);
return 1;
}
int
lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t bump)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lwsl_sspc_notice(h, "%d\n", (int)bump);
return _lws_sspc_set_metadata(h, "", NULL, 0, (int)bump);
}
int
lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
return h->txc.peer_tx_cr_est;
}
void
lws_sspc_start_timeout(struct lws_sspc_handle *h, unsigned int timeout_ms)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (!h->txp_path.priv_onw)
/* we can't fulfil it */
return;
h->timeout_ms = (uint32_t)timeout_ms;
h->pending_timeout_update = 1;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
}
void
lws_sspc_cancel_timeout(struct lws_sspc_handle *h)
{
lws_sspc_start_timeout(h, (unsigned int)-1);
}
void *
lws_sspc_to_user_object(struct lws_sspc_handle *h)
{
return (void *)(h + 1);
}
struct lws_log_cx *
lwsl_sspc_get_cx(struct lws_sspc_handle *sspc)
{
if (!sspc)
return NULL;
return sspc->lc.log_cx;
}
void
lws_log_prepend_sspc(struct lws_log_cx *cx, void *obj, char **p, char *e)
{
struct lws_sspc_handle *h = (struct lws_sspc_handle *)obj;
#if defined(STANDALONE)
snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ", h->lc.gutag);
#else
*p += lws_snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ",
lws_sspc_tag(h));
#endif
}
void
lws_sspc_change_handlers(struct lws_sspc_handle *h, lws_sscb_rx rx,
lws_sscb_tx tx, lws_sscb_state state)
{
if (rx)
h->ssi.rx = rx;
if (tx)
h->ssi.tx = tx;
if (state)
h->ssi.state = state;
}
const char *
lws_sspc_tag(struct lws_sspc_handle *h)
{
if (!h)
return "[null sspc]";
#if defined(STANDALONE)
return h->lc.gutag;
#else
return lws_lc_tag(&h->lc);
#endif
}
int
lws_sspc_cancel_notify_dll(struct lws_dll2 *d, void *user)
{
lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t,
client_list);
lws_sspc_event_helper(h, LWSSSCS_EVENT_WAIT_CANCELLED, 0);
return 0;
}