ss: policy: response code mapping
This adds a per-streamtype JSON mapping table in the policy.
In addition to the previous flow, it lets you generate custom
SS state notifications for specific http response codes, eg:
"http_resp_map": [ { "530": 1530 }, { "531": 1531 } ],
It's not recommended to overload the transport-layer response
code with application layer responses. It's better to return
a 200 and then in the application protocol inside http, explain
what happened from the application perspective, usually with
JSON. But this is designed to let you handle existing systems
that do overload the transport layer response code.
SS states for user use start at LWSSSCS_USER_BASE, which is
1000.
You can do a basic test with minimal-secure-streams and --respmap
flag, this will go to httpbin.org and get a 404, and the warmcat.com
policy has the mapping for 404 -> LWSSSCS_USER_BASE (1000).
Since the mapping emits states, these are serialized and handled
like any other state in the proxy case.
The policy2c example / tool is also updated to handle the additional
mapping tables.
diff --git a/include/libwebsockets/lws-lejp.h b/include/libwebsockets/lws-lejp.h
index cd90777..55c4dc0 100644
--- a/include/libwebsockets/lws-lejp.h
+++ b/include/libwebsockets/lws-lejp.h
@@ -182,7 +182,7 @@
#define LEJP_MAX_DEPTH 12
#endif
#ifndef LEJP_MAX_INDEX_DEPTH
-#define LEJP_MAX_INDEX_DEPTH 5
+#define LEJP_MAX_INDEX_DEPTH 6
#endif
#ifndef LEJP_MAX_PATH
#define LEJP_MAX_PATH 128
diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h
index ac73d0e..c8a06be 100644
--- a/include/libwebsockets/lws-secure-streams-policy.h
+++ b/include/libwebsockets/lws-secure-streams-policy.h
@@ -173,6 +173,10 @@
#endif
} lws_ss_metadata_t;
+typedef struct lws_ss_http_respmap {
+ uint16_t resp; /* the http response code */
+ uint16_t state; /* low 16-bits of associated state */
+} lws_ss_http_respmap_t;
/**
* lws_ss_policy_t: policy database entry for a stream type
@@ -221,6 +225,8 @@
const char *blob_header[_LWSSS_HBI_COUNT];
const char *auth_preamble;
+ const lws_ss_http_respmap_t *respmap;
+
union {
// struct { /* LWSSSP_H1 */
// } h1;
@@ -234,6 +240,7 @@
} u;
uint16_t resp_expect;
+ uint8_t count_respmap;
uint8_t fail_redirect:1;
} http;
diff --git a/include/libwebsockets/lws-secure-streams.h b/include/libwebsockets/lws-secure-streams.h
index 4cffc6d..c613a70 100644
--- a/include/libwebsockets/lws-secure-streams.h
+++ b/include/libwebsockets/lws-secure-streams.h
@@ -200,6 +200,8 @@
* stream joins the sink */
LWSSSCS_SINK_PART, /* sinks get this when a new source
* stream leaves the sink */
+
+ LWSSSCS_USER_BASE = 1000
} lws_ss_constate_t;
enum {
diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md
index a5b1df8..339645d 100644
--- a/lib/secure-streams/README.md
+++ b/lib/secure-streams/README.md
@@ -389,6 +389,21 @@
same metadata name. The metadata names must be listed in the
"metadata": [ ] section.
+### `http_resp_map`
+
+If your server overloads the meaning of the http transport response code with
+server-custom application codes, you can map these to discrete Secure Streams
+state callbacks using a JSON map, eg
+
+```
+ "http_resp_map": [ { "530": 1530 }, { "531": 1531 } ],
+```
+
+It's not recommended to abuse the transport layer http response code by
+mixing it with application state information like this, but if it's dealing
+with legacy serverside that takes this approach, it's possible to handle it
+in SS this way while removing the dependency on http.
+
### `http_auth_header`
The name of the header that takes the auth token, with a trailing ':', eg
diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c
index d0a452f..23b112f 100644
--- a/lib/secure-streams/policy-json.c
+++ b/lib/secure-streams/policy-json.c
@@ -59,6 +59,8 @@
"s[].*.tls_trust_store",
"s[].*.metadata",
"s[].*.metadata[].*",
+ "s[].*.http_resp_map",
+ "s[].*.http_resp_map[].*",
"s[].*.http_auth_header",
"s[].*.http_dsn_header",
@@ -132,6 +134,8 @@
LSSPPT_TRUST,
LSSPPT_METADATA,
LSSPPT_METADATA_ITEM,
+ LSSPPT_HTTPRESPMAP,
+ LSSPPT_HTTPRESPMAP_ITEM,
LSSPPT_HTTP_AUTH_HEADER,
LSSPPT_HTTP_DSN_HEADER,
@@ -231,7 +235,8 @@
if (reason == LEJPCB_ARRAY_START &&
(ctx->path_match - 1 == LSSPPT_PLUGINS ||
- ctx->path_match - 1 == LSSPPT_METADATA))
+ ctx->path_match - 1 == LSSPPT_METADATA ||
+ ctx->path_match - 1 == LSSPPT_HTTPRESPMAP))
a->count = 0;
if (reason == LEJPCB_ARRAY_END &&
@@ -241,6 +246,25 @@
goto oom;
}
+ if (reason == LEJPCB_ARRAY_END && a->count && a->pending_respmap) {
+
+ // lwsl_notice("%s: allocating respmap %d\n", __func__, a->count);
+
+ a->curr[LTY_POLICY].p->u.http.respmap = lwsac_use_zero(&a->ac,
+ sizeof(lws_ss_http_respmap_t) * a->count, POL_AC_GRAIN);
+
+ if (!a->curr[LTY_POLICY].p->u.http.respmap)
+ goto oom;
+
+ memcpy((void *)a->curr[LTY_POLICY].p->u.http.respmap,
+ a->respmap, sizeof(lws_ss_http_respmap_t) * a->count);
+ a->curr[LTY_POLICY].p->u.http.count_respmap = (uint8_t)a->count;
+ a->count = 0;
+ a->pending_respmap = 0;
+
+ return 0;
+ }
+
if (reason == LEJPCB_OBJECT_END && a->p) {
/*
* Allocate a just-the-right-size buf for the cert DER now
@@ -297,7 +321,8 @@
if (p2) /* we may be overriding existing streamtype... */
a->curr[n].b = (backoff_t *)p2;
else
- a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN);
+ a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n],
+ POL_AC_GRAIN);
if (!a->curr[n].b)
goto oom;
@@ -659,6 +684,19 @@
break;
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+
+ case LSSPPT_HTTPRESPMAP_ITEM:
+ if (a->count >= (int)LWS_ARRAY_SIZE(a->respmap)) {
+ lwsl_err("%s: respmap too big\n", __func__);
+ return -1;
+ }
+ a->respmap[a->count].resp =
+ atoi(ctx->path + ctx->st[ctx->sp - 2].p + 1);
+ a->respmap[a->count].state = atoi(ctx->buf);
+ a->pending_respmap = 1;
+ a->count++;
+ break;
+
case LSSPPT_HTTP_AUTH_HEADER:
case LSSPPT_HTTP_DSN_HEADER:
case LSSPPT_HTTP_FWV_HEADER:
diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h
index 07bee1b..d44ca25 100644
--- a/lib/secure-streams/private-lib-secure-streams.h
+++ b/lib/secure-streams/private-lib-secure-streams.h
@@ -325,12 +325,15 @@
struct lws_b64state b64;
+ lws_ss_http_respmap_t respmap[16];
+
union u heads[_LTY_COUNT];
union u curr[_LTY_COUNT];
uint8_t *p;
int count;
+ char pending_respmap;
};
#if defined(LWS_WITH_SYS_SMD)
diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c
index 8055ab8..b278f68 100644
--- a/lib/secure-streams/protocols/ss-h1.c
+++ b/lib/secure-streams/protocols/ss-h1.c
@@ -153,6 +153,25 @@
#endif
/*
+ * Returns 0, or the ss state resp maps on to
+ */
+
+static int
+lws_ss_http_resp_to_state(lws_ss_handle_t *h, int resp)
+{
+ const lws_ss_http_respmap_t *r = h->policy->u.http.respmap;
+ int n = h->policy->u.http.count_respmap;
+
+ while (n--)
+ if (resp == r->resp)
+ return r->state;
+ else
+ r++;
+
+ return 0; /* no hit */
+}
+
+/*
* This converts any set metadata items into outgoing http headers
*/
@@ -404,6 +423,23 @@
return -1;
}
+ if (status) {
+ /*
+ * Check and see if it's something from the response
+ * map, if so, generate the requested status. If we're
+ * the proxy onward connection, metadata has priority
+ * over state updates on the serialization, so the
+ * state callback will see the right metadata.
+ */
+ int n = lws_ss_http_resp_to_state(h, status);
+ if (n) {
+ r = lws_ss_event_helper(h, n);
+ if (r != LWSSSSRET_OK)
+ return _lws_ss_handle_state_ret(r, wsi,
+ &h);
+ }
+ }
+
if (h->u.http.good_respcode)
lwsl_info("%s: Connected streamtype %s, %d\n", __func__,
h->policy->streamtype, status);
diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c
index 7fceb01..7a5737b 100644
--- a/lib/secure-streams/secure-streams.c
+++ b/lib/secure-streams/secure-streams.c
@@ -71,6 +71,9 @@
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";
diff --git a/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c
index fa33cfc..dbe0f79 100644
--- a/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c
+++ b/minimal-examples/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c
@@ -180,14 +180,15 @@
printf("\t.next = (void *)&%s, \n", prev);
printf("\t.name = \"%s\",\n", (const char *)md->name);
- if (md->value)
+ if (md->value) {
printf("\t.value = (void *)\"%s\",\n",
(const char *)md->value);
-
- printf("\t.length = %d,\n", idx++); // md->length);
- printf("\t.value_length = 0x%x,\n",
+ printf("\t.value_length = 0x%x,\n",
(unsigned int)strlen(
(const char *)md->value));
+ }
+
+ printf("\t.length = %d,\n", idx++); // md->length);
printf("\t.value_is_http_token = 0x%x,\n",
(unsigned int)md->value_is_http_token);
printf("}");
@@ -361,7 +362,49 @@
pol = pol->next;
}
+ /* dump any streamtype's http resp map */
+ pol = lws_ss_policy_get(context);
+ m = 0;
+
+ while (pol) {
+
+ lws_snprintf(curr, sizeof(curr), "_ssp_%s",
+ purify_csymbol(pol->streamtype, buf, sizeof(buf)));
+
+ /* if relevant, dump http resp map */
+
+ switch (pol->protocol) {
+ case LWSSSP_H1:
+ case LWSSSP_H2:
+ case LWSSSP_WS:
+
+ if (!pol->u.http.count_respmap)
+ break;
+
+ if (!m)
+ printf("\nstatic const lws_ss_http_respmap_t ");
+ else
+ printf(",\n");
+ m++;
+
+ printf("%s_http_respmap[] = {\n", curr);
+ for (n = 0; n < pol->u.http.count_respmap; n++) {
+ printf("\t{ %d, 0x%x },\n",
+ pol->u.http.respmap[n].resp,
+ pol->u.http.respmap[n].state);
+
+ est += sizeof(lws_ss_http_respmap_t);
+ }
+ printf("}");
+ break;
+ }
+
+ pol = pol->next;
+ }
+
+ if (m)
+ printf(";\n");
/*
* The streamtypes
@@ -378,8 +421,8 @@
lws_snprintf(curr, sizeof(curr), "_ssp_%s",
purify_csymbol(pol->streamtype, buf, sizeof(buf)));
- printf("%s = {\n", curr);
+ printf("%s = {\n", curr);
if (prev[0])
printf("\t.next = (void *)&%s,\n", prev);
@@ -437,6 +480,13 @@
printf("\t\t\t.auth_preamble = \"%s\",\n",
pol->u.http.auth_preamble);
+ if (pol->u.http.respmap) {
+ printf("\t\t\t.respmap = &%s_http_respmap,\n",
+ curr);
+ printf("\t\t\t.count_respmap = %d,\n",
+ pol->u.http.count_respmap);
+ }
+
if (pol->u.http.blob_header[0]) {
printf("\t\t\t.blob_header = {\n");
for (n = 0; n < (int)LWS_ARRAY_SIZE(pol->u.http.blob_header); n++)
diff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c
index 2620975..fd8f8fa 100644
--- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c
+++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c
@@ -38,7 +38,7 @@
// #define VIA_LOCALHOST_SOCKS
static int interrupted, bad = 1, force_cpd_fail_portal,
- force_cpd_fail_no_internet;
+ force_cpd_fail_no_internet, test_respmap;
static unsigned int timeout_ms = 3000;
static lws_state_notify_link_t nl;
@@ -169,7 +169,7 @@
#if defined(VIA_LOCALHOST_SOCKS)
"\"http_url\":" "\"policy/minimal-proxy-socks.json\","
#else
- "\"http_url\":" "\"policy/minimal-proxy.json\","
+ "\"http_url\":" "\"policy/minimal-proxy-2.json\","
#endif
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
@@ -271,8 +271,8 @@
{
myss_t *m = (myss_t *)userobj;
- lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
- (unsigned int)ack);
+ lwsl_user("%s: %s (%d), ord 0x%x\n", __func__,
+ lws_ss_state_name(state), state, (unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
@@ -292,6 +292,11 @@
case LWSSSCS_TIMEOUT:
lwsl_notice("%s: LWSSSCS_TIMEOUT\n", __func__);
break;
+
+ case LWSSSCS_USER_BASE:
+ lwsl_notice("%s: LWSSSCS_USER_BASE\n", __func__);
+ break;
+
default:
break;
}
@@ -383,7 +388,7 @@
ssi.tx = myss_tx;
ssi.state = myss_state;
ssi.user_alloc = sizeof(myss_t);
- ssi.streamtype = "mintest";
+ ssi.streamtype = test_respmap ? "respmap" : "mintest";
if (lws_ss_create(context, 0, &ssi, NULL, NULL,
NULL, NULL)) {
@@ -430,6 +435,9 @@
if (lws_cmdline_option(argc, argv, "--force-no-internet"))
force_cpd_fail_no_internet = 1;
+ if (lws_cmdline_option(argc, argv, "--respmap"))
+ test_respmap = 1;
+
if ((p = lws_cmdline_option(argc, argv, "--timeout_ms")))
timeout_ms = atoi(p);