android13/external/libwebsockets/lib/abstract/transports/unit-test.c

541 lines
12 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 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.
*
* 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 provided canned packets from an array of test vectors.
*/
#include "private-lib-core.h"
#include "private-lib-abstract.h"
/* this is the transport priv instantiated at abs->ati */
typedef struct lws_abstxp_unit_test_priv {
char note[128];
struct lws_abs *abs;
struct lws_sequencer *seq;
lws_unit_test_t *current_test;
lws_unit_test_packet_t *expect;
lws_unit_test_packet_test_cb result_cb;
const void *result_cb_arg;
lws_unit_test_packet_disposition disposition;
/* synthesized protocol timeout */
time_t timeout;
uint8_t established:1;
uint8_t connecting:1;
} abs_unit_test_priv_t;
typedef struct seq_priv {
lws_abs_t *ai;
} seq_priv_t;
enum {
UTSEQ_MSG_WRITEABLE = LWSSEQ_USER_BASE,
UTSEQ_MSG_CLOSING,
UTSEQ_MSG_TIMEOUT,
UTSEQ_MSG_CONNECTING,
UTSEQ_MSG_POST_TX_KICK,
UTSEQ_MSG_DISPOSITION_KNOWN
};
/*
* A definitive result has appeared for the current test
*/
static lws_unit_test_packet_disposition
lws_unit_test_packet_dispose(abs_unit_test_priv_t *priv,
lws_unit_test_packet_disposition disp,
const char *note)
{
assert(priv->disposition == LPE_CONTINUE);
lwsl_info("%s: %d\n", __func__, disp);
if (note)
lws_strncpy(priv->note, note, sizeof(priv->note));
priv->disposition = disp;
lws_seq_queue_event(priv->seq, UTSEQ_MSG_DISPOSITION_KNOWN,
NULL, NULL);
return disp;
}
/*
* start on the next step of the test
*/
lws_unit_test_packet_disposition
process_expect(abs_unit_test_priv_t *priv)
{
assert(priv->disposition == LPE_CONTINUE);
while (priv->expect->flags & LWS_AUT_EXPECT_RX &&
priv->disposition == LPE_CONTINUE) {
int f = priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE, s;
if (priv->expect->pre)
priv->expect->pre(priv->abs);
lwsl_info("%s: rx()\n", __func__);
lwsl_hexdump_debug(priv->expect->buffer, priv->expect->len);
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_unit_test_packet_dispose(priv, LPE_FAILED,
"rx unexpected return");
}
if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) {
lws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL);
break;
}
priv->expect++;
}
return LPE_CONTINUE;
}
static lws_seq_cb_return_t
unit_test_sequencer_cb(struct lws_sequencer *seq, void *user, int event,
void *data, void *aux)
{
seq_priv_t *s = (seq_priv_t *)user;
abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)s->ai->ati;
time_t now;
switch ((int)event) {
case LWSSEQ_CREATED: /* our sequencer just got started */
lwsl_notice("%s: %s: created\n", __func__,
lws_seq_name(seq));
if (s->ai->at->client_conn(s->ai)) {
lwsl_notice("%s: %s: abstract client conn failed\n",
__func__, lws_seq_name(seq));
return LWSSEQ_RET_DESTROY;
}
break;
case LWSSEQ_DESTROYED:
/*
* This sequencer is about to be destroyed. If we have any
* other assets in play, detach them from us.
*/
if (priv->abs)
lws_abs_destroy_instance(&priv->abs);
break;
case LWSSEQ_HEARTBEAT:
/* synthesize a wsi-style timeout */
if (!priv->timeout)
goto ph;
time(&now);
if (now <= priv->timeout)
goto ph;
if (priv->expect->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT) {
lwsl_user("%s: test got expected timeout\n",
__func__);
lws_unit_test_packet_dispose(priv,
LPE_FAILED_UNEXPECTED_TIMEOUT, NULL);
return LWSSEQ_RET_DESTROY;
}
lwsl_user("%s: seq timed out\n", __func__);
ph:
if (priv->abs->ap->heartbeat)
priv->abs->ap->heartbeat(priv->abs->api);
break;
case UTSEQ_MSG_DISPOSITION_KNOWN:
lwsl_info("%s: %s: DISPOSITION_KNOWN %s: %s\n", __func__,
priv->abs->ap->name,
priv->current_test->name,
priv->disposition == LPE_SUCCEEDED ? "OK" : "FAIL");
/*
* if the test has a callback, call it back to let it
* know the result
*/
if (priv->result_cb)
priv->result_cb(priv->result_cb_arg, priv->disposition);
return LWSSEQ_RET_DESTROY;
case UTSEQ_MSG_CONNECTING:
lwsl_debug("UTSEQ_MSG_CONNECTING\n");
if (priv->abs->ap->accept)
priv->abs->ap->accept(priv->abs->api);
priv->established = 1;
/* fallthru */
case UTSEQ_MSG_POST_TX_KICK:
if (priv->disposition)
break;
if (process_expect(priv) != LPE_CONTINUE) {
lwsl_notice("%s: UTSEQ_MSG_POST_TX_KICK failed\n",
__func__);
return LWSSEQ_RET_DESTROY;
}
break;
case UTSEQ_MSG_WRITEABLE:
/*
* inform the protocol our transport is writeable now
*/
priv->abs->ap->writeable(priv->abs->api, 1024);
break;
case UTSEQ_MSG_CLOSING:
if (!(priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE)) {
lwsl_user("%s: got unexpected close\n", __func__);
lws_unit_test_packet_dispose(priv,
LPE_FAILED_UNEXPECTED_CLOSE, NULL);
goto done;
}
/* tell the abstract protocol we are closing on them */
if (priv->abs && priv->abs->ap->closed)
priv->abs->ap->closed(priv->abs->api);
goto done;
case UTSEQ_MSG_TIMEOUT: /* current step timed out */
s->ai->at->close(s->ai->ati);
if (!(priv->expect->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT)) {
lwsl_user("%s: got unexpected timeout\n", __func__);
lws_unit_test_packet_dispose(priv,
LPE_FAILED_UNEXPECTED_TIMEOUT, NULL);
return LWSSEQ_RET_DESTROY;
}
goto done;
done:
lws_seq_timeout_us(lws_seq_from_user(s),
LWSSEQTO_NONE);
priv->expect++;
if (!priv->expect->buffer) {
/* the sequence has completed */
lwsl_user("%s: sequence completed OK\n", __func__);
return LWSSEQ_RET_DESTROY;
}
break;
default:
break;
}
return LWSSEQ_RET_CONTINUE;
}
static int
lws_atcut_close(lws_abs_transport_inst_t *ati)
{
abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati;
lwsl_notice("%s\n", __func__);
lws_seq_queue_event(priv->seq, UTSEQ_MSG_CLOSING, NULL, NULL);
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);
lwsl_info("%s: received tx\n", __func__);
if (priv->expect->pre)
priv->expect->pre(priv->abs);
if (!(priv->expect->flags & LWS_AUT_EXPECT_TX)) {
lwsl_notice("%s: unexpected tx\n", __func__);
lwsl_hexdump_notice(buf, len);
lws_unit_test_packet_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_unit_test_packet_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_debug(priv->expect->buffer, len);
lwsl_hexdump_debug(buf, len);
lws_unit_test_packet_dispose(priv, LPE_FAILED,
"tx data mismatch");
return 1;
}
if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) {
lws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL);
return 1;
}
priv->expect++;
lws_seq_queue_event(priv->seq, UTSEQ_MSG_POST_TX_KICK, NULL, NULL);
return 0;
}
#if defined(LWS_WITH_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;
if (priv->established) {
lwsl_err("%s: already established\n", __func__);
return 1;
}
/* set up the test start pieces... the array of test expects... */
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_unit_test_t *)tm->u.value;
/* ... and the callback to deliver the result to */
tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_RESULT_CB);
if (tm)
priv->result_cb = (lws_unit_test_packet_test_cb)tm->u.value;
else
priv->result_cb = NULL;
/* ... and the arg to deliver it with */
tm = lws_abs_get_token(abs->at_tokens,
LTMI_PEER_V_EXPECT_RESULT_CB_ARG);
if (tm)
priv->result_cb_arg = tm->u.value;
priv->expect = priv->current_test->expect_array;
priv->disposition = LPE_CONTINUE;
priv->note[0] = '\0';
lws_seq_timeout_us(priv->seq, priv->current_test->max_secs *
LWS_US_PER_SEC);
lwsl_notice("%s: %s: test '%s': start\n", __func__, abs->ap->name,
priv->current_test->name);
lws_seq_queue_event(priv->seq, UTSEQ_MSG_CONNECTING, NULL, NULL);
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;
/*
* Queue a writeable event... this won't be handled by teh sequencer
* until we have returned to the event loop, just like a real
* callback_on_writable()
*/
lws_seq_queue_event(priv->seq, UTSEQ_MSG_WRITEABLE, NULL, NULL);
return 0;
}
/*
* An abstract protocol + transport has been instantiated
*/
static int
lws_atcut_create(lws_abs_t *ai)
{
abs_unit_test_priv_t *priv;
struct lws_sequencer *seq;
lws_seq_info_t i;
seq_priv_t *s;
memset(&i, 0, sizeof(i));
i.context = ai->vh->context;
i.user_size = sizeof(*s);
i.puser = (void **)&s;
i.cb = unit_test_sequencer_cb;
i.name = "unit-test-seq";
/*
* Create the sequencer for the steps in a single unit test
*/
seq = lws_seq_create(&i);
if (!seq) {
lwsl_err("%s: unable to create sequencer\n", __func__);
return 1;
}
priv = ai->ati;
memset(s, 0, sizeof(*s));
memset(priv, 0, sizeof(*priv));
/* the sequencer priv just points to the lws_abs_t */
s->ai = ai;
priv->abs = ai;
priv->seq = seq;
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;
time_t now;
time(&now);
if (secs)
priv->timeout = now + secs;
else
priv->timeout = 0;
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;
}
static const char *dnames[] = {
"INCOMPLETE",
"PASS",
"FAIL",
"FAIL(TIMEOUT)",
"FAIL(UNEXPECTED PASS)",
"FAIL(UNEXPECTED CLOSE)",
"SKIPPED"
"?",
"?"
};
const char *
lws_unit_test_result_name(int in)
{
if (in < 0 || in > (int)LWS_ARRAY_SIZE(dnames))
return "unknown";
return dnames[in];
}
static int
lws_atcut_compare(lws_abs_t *abs1, lws_abs_t *abs2)
{
return 0;
}
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,
.compare = lws_atcut_compare,
.tx = lws_atcut_tx,
#if !defined(LWS_WITH_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,
};