mqtt: drain queued output before advancing the state machine
No binary (build failed)
—
(log yok)
| @@ -67,8 +67,10 @@ enum mqttstate { | |
| MQTT_SUBACK_COMING, /* 4 - the SUBACK remainder */ | |
| MQTT_PUBWAIT, /* 5 - wait for publish */ | |
| MQTT_PUB_REMAIN, /* 6 - wait for the remainder of the publish */ | |
| + | MQTT_POST_DRAIN, /* 7 - finish sending PUBLISH */ |
| + | MQTT_DISCONNECT_DRAIN, /* 8 - finish sending DISCONNECT */ |
| - | MQTT_NOSTATE /* 7 - never used an actual state */ |
| + | MQTT_NOSTATE /* 9 - never used an actual state */ |
| }; | |
| struct mqtt_conn { | |
| @@ -140,32 +142,48 @@ static CURLcode mqtt_send(struct Curl_easy *data, | |
| if(!mq) | |
| return CURLE_FAILED_INIT; | |
| + | /* A new packet must not replace a previously queued packet tail. */ |
| + | DEBUGASSERT(!curlx_dyn_len(&mq->sendbuf)); |
| result = Curl_xfer_send(data, buf, len, FALSE, &n); | |
| if(result) | |
| return result; | |
| mq->lastTime = *Curl_pgrs_now(data); | |
| Curl_debug(data, CURLINFO_HEADER_OUT, buf, n); | |
| - | if(len != n) { |
| - | size_t nsend = len - n; |
| - | if(curlx_dyn_len(&mq->sendbuf)) { |
| - | DEBUGASSERT(curlx_dyn_len(&mq->sendbuf) >= nsend); |
| - | result = curlx_dyn_tail(&mq->sendbuf, nsend); /* keep this much */ |
| - | } |
| - | else { |
| - | result = curlx_dyn_addn(&mq->sendbuf, &buf[n], nsend); |
| - | } |
| - | } |
| - | else |
| - | curlx_dyn_reset(&mq->sendbuf); |
| + | if(len != n) |
| + | result = curlx_dyn_addn(&mq->sendbuf, &buf[n], len - n); |
| return result; | |
| } | |
| + | /* Send the queued tail, preserving any bytes that still cannot be sent. */ |
| + | static CURLcode mqtt_flush(struct Curl_easy *data) |
| + | { |
| + | struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); |
| + | size_t len, n; |
| + | CURLcode result; |
| + | |
| + | if(!mq) |
| + | return CURLE_FAILED_INIT; |
| + | len = curlx_dyn_len(&mq->sendbuf); |
| + | if(!len) |
| + | return CURLE_OK; |
| + | |
| + | result = Curl_xfer_send(data, curlx_dyn_ptr(&mq->sendbuf), len, FALSE, &n); |
| + | if(result) |
| + | return result; |
| + | mq->lastTime = *Curl_pgrs_now(data); |
| + | Curl_debug(data, CURLINFO_HEADER_OUT, curlx_dyn_ptr(&mq->sendbuf), n); |
| + | return curlx_dyn_tail(&mq->sendbuf, len - n); |
| + | } |
| + | |
| /* Generic function called by the multi interface to figure out what socket(s) | |
| to wait for and for what actions during the DOING and PROTOCONNECT | |
| states */ | |
| static CURLcode mqtt_pollset(struct Curl_easy *data, | |
| struct easy_pollset *ps) | |
| { | |
| + | struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); |
| + | if(mq && curlx_dyn_len(&mq->sendbuf)) |
| + | return Curl_pollset_add_out(data, ps, data->conn->sock[FIRSTSOCKET]); |
| return Curl_pollset_add_in(data, ps, data->conn->sock[FIRSTSOCKET]); | |
| } | |
| @@ -637,6 +655,8 @@ static const char * const statenames[] = { | |
| "MQTT_SUBACK_COMING", | |
| "MQTT_PUBWAIT", | |
| "MQTT_PUB_REMAIN", | |
| + | "MQTT_POST_DRAIN", |
| + | "MQTT_DISCONNECT_DRAIN", |
| "NOT A STATE" | |
| }; | |
| @@ -843,14 +863,16 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) | |
| if(curlx_dyn_len(&mq->sendbuf)) { | |
| /* send the remainder of an outgoing packet */ | |
| - | result = mqtt_send(data, curlx_dyn_ptr(&mq->sendbuf), |
| - | curlx_dyn_len(&mq->sendbuf)); |
| - | if(result) |
| + | result = mqtt_flush(data); |
| + | /* CURLE_OK can still mean a short write. Wait for writable progress |
| + | before sending another packet or processing a reply. */ |
| + | if(result || curlx_dyn_len(&mq->sendbuf)) |
| return result; | |
| } | |
| result = mqtt_ping(data); | |
| - | if(result) |
| + | /* Packet processing may send more output, so finish PINGREQ first. */ |
| + | if(result || curlx_dyn_len(&mq->sendbuf)) |
| return result; | |
| infof(data, "mqtt_doing: state [%d]", (int)mqtt->state); | |
| @@ -935,20 +957,31 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) | |
| if(result) | |
| break; | |
| - | if(data->state.httpreq == HTTPREQ_POST) { |
| - | result = mqtt_publish(data); |
| - | if(!result) { |
| - | result = mqtt_disconnect(data); |
| - | *done = TRUE; |
| - | } |
| - | mqtt->nextstate = MQTT_FIRST; |
| - | } |
| - | else { |
| + | if(data->state.httpreq != HTTPREQ_POST) { |
| result = mqtt_subscribe(data); | |
| - | if(!result) { |
| + | if(!result) |
| mqstate(data, MQTT_FIRST, MQTT_SUBACK); | |
| - | } |
| + | break; |
| } | |
| + | |
| + | result = mqtt_publish(data); |
| + | if(result) |
| + | break; |
| + | mqstate(data, MQTT_POST_DRAIN, MQTT_NOSTATE); |
| + | FALLTHROUGH(); |
| + | case MQTT_POST_DRAIN: |
| + | /* PUBLISH must be complete before DISCONNECT can use the send buffer. */ |
| + | if(curlx_dyn_len(&mq->sendbuf)) |
| + | break; |
| + | result = mqtt_disconnect(data); |
| + | if(result) |
| + | break; |
| + | mqstate(data, MQTT_DISCONNECT_DRAIN, MQTT_NOSTATE); |
| + | FALLTHROUGH(); |
| + | case MQTT_DISCONNECT_DRAIN: |
| + | /* Completing now would discard any DISCONNECT bytes still queued. */ |
| + | if(!curlx_dyn_len(&mq->sendbuf)) |
| + | *done = TRUE; |
| break; | |
| case MQTT_SUBACK: | |
| @@ -1711,6 +1711,7 @@ TESTCASES = \ | |
| test1690 \ | |
| test1691 \ | |
| test1692 \ | |
| + | test1693 \ |
| \ | |
| test1700 \ | |
| test1702 \ | |
| @@ -1937,6 +1938,10 @@ TESTCASES = \ | |
| test2206 \ | |
| test2207 \ | |
| test2208 \ | |
| + | test2209 \ |
| + | test2210 \ |
| + | test2211 \ |
| + | test2212 \ |
| \ | |
| test2270 \ | |
| test2271 \ | |
| @@ -0,0 +1,19 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | unittest |
| + | MQTT |
| + | </keywords> |
| + | </info> |
| + | |
| + | <client> |
| + | <features> |
| + | unittest |
| + | mqtt |
| + | </features> |
| + | <name> |
| + | MQTT partial sends, would-block, polling and packet ordering |
| + | </name> |
| + | </client> |
| + | </testcase> |
| @@ -0,0 +1,46 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | MQTT |
| + | MQTT PUBLISH |
| + | </keywords> |
| + | </info> |
| + | |
| + | <reply> |
| + | <data> |
| + | </data> |
| + | </reply> |
| + | |
| + | <client> |
| + | <features> |
| + | mqtt |
| + | Debug |
| + | </features> |
| + | <server> |
| + | mqtt |
| + | </server> |
| + | <name> |
| + | MQTT PUBLISH with partial writes |
| + | </name> |
| + | <setenv> |
| + | CURL_DBG_SOCK_WPARTIAL=50 |
| + | </setenv> |
| + | <command option="binary-trace"> |
| + | mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something --max-time 10 |
| + | </command> |
| + | </client> |
| + | |
| + | <verify> |
| + | # Strip the random part of the client ID. |
| + | <strippart> |
| + | s/^(.* 00044d5154540402003c000c6375726c).*/$1/ |
| + | </strippart> |
| + | <protocol> |
| + | client CONNECT 18 00044d5154540402003c000c6375726c |
| + | server CONNACK 2 20020000 |
| + | client PUBLISH f 000432323039736f6d657468696e67 |
| + | client DISCONNECT 0 e000 |
| + | </protocol> |
| + | </verify> |
| + | </testcase> |
| @@ -0,0 +1,49 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | MQTT |
| + | MQTT SUBSCRIBE |
| + | </keywords> |
| + | </info> |
| + | |
| + | <reply> |
| + | <data nocheck="yes"> |
| + | hello |
| + | </data> |
| + | </reply> |
| + | |
| + | <client> |
| + | <features> |
| + | mqtt |
| + | Debug |
| + | </features> |
| + | <server> |
| + | mqtt |
| + | </server> |
| + | <name> |
| + | MQTT SUBSCRIBE with partial writes |
| + | </name> |
| + | <setenv> |
| + | CURL_DBG_SOCK_WPARTIAL=50 |
| + | </setenv> |
| + | <command option="binary-trace"> |
| + | mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER --max-time 10 |
| + | </command> |
| + | </client> |
| + | |
| + | <verify> |
| + | # Strip the random part of the client ID. |
| + | <strippart> |
| + | s/^(.* 00044d5154540402003c000c6375726c).*/$1/ |
| + | </strippart> |
| + | <protocol> |
| + | client CONNECT 18 00044d5154540402003c000c6375726c |
| + | server CONNACK 2 20020000 |
| + | client SUBSCRIBE 9 000100043232313000 |
| + | server SUBACK 3 9003000100 |
| + | server PUBLISH c 300c00043232313068656c6c6f0a |
| + | server DISCONNECT 0 e000 |
| + | </protocol> |
| + | </verify> |
| + | </testcase> |
| @@ -0,0 +1,48 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | MQTT |
| + | MQTT PUBLISH |
| + | MQTTS |
| + | </keywords> |
| + | </info> |
| + | |
| + | <reply> |
| + | <data> |
| + | </data> |
| + | </reply> |
| + | |
| + | <client> |
| + | <features> |
| + | mqtts |
| + | Debug |
| + | </features> |
| + | <server> |
| + | mqtts |
| + | </server> |
| + | <name> |
| + | MQTTS PUBLISH with partial writes |
| + | </name> |
| + | <setenv> |
| + | # Split MQTT packets, including DISCONNECT, without fragmenting TLS handshakes. |
| + | CURL_SMALLSENDS=1 |
| + | </setenv> |
| + | <command option="binary-trace"> |
| + | mqtts://%HOSTIP:%MQTTSPORT/%TESTNUMBER -d something --insecure --max-time 10 |
| + | </command> |
| + | </client> |
| + | |
| + | <verify> |
| + | # Strip the random part of the client ID. |
| + | <strippart> |
| + | s/^(.* 00044d5154540402003c000c6375726c).*/$1/ |
| + | </strippart> |
| + | <protocol> |
| + | client CONNECT 18 00044d5154540402003c000c6375726c |
| + | server CONNACK 2 20020000 |
| + | client PUBLISH f 000432323131736f6d657468696e67 |
| + | client DISCONNECT 0 e000 |
| + | </protocol> |
| + | </verify> |
| + | </testcase> |
| @@ -0,0 +1,51 @@ | |
| + | <?xml version="1.0" encoding="US-ASCII"?> |
| + | <testcase> |
| + | <info> |
| + | <keywords> |
| + | MQTT |
| + | MQTT SUBSCRIBE |
| + | MQTTS |
| + | </keywords> |
| + | </info> |
| + | |
| + | <reply> |
| + | <data nocheck="yes"> |
| + | hello |
| + | </data> |
| + | </reply> |
| + | |
| + | <client> |
| + | <features> |
| + | mqtts |
| + | Debug |
| + | </features> |
| + | <server> |
| + | mqtts |
| + | </server> |
| + | <name> |
| + | MQTTS SUBSCRIBE with partial writes |
| + | </name> |
| + | <setenv> |
| + | # Split MQTT packets without fragmenting TLS handshakes. |
| + | CURL_SMALLSENDS=1 |
| + | </setenv> |
| + | <command option="binary-trace"> |
| + | mqtts://%HOSTIP:%MQTTSPORT/%TESTNUMBER --insecure --max-time 10 |
| + | </command> |
| + | </client> |
| + | |
| + | <verify> |
| + | # Strip the random part of the client ID. |
| + | <strippart> |
| + | s/^(.* 00044d5154540402003c000c6375726c).*/$1/ |
| + | </strippart> |
| + | <protocol> |
| + | client CONNECT 18 00044d5154540402003c000c6375726c |
| + | server CONNACK 2 20020000 |
| + | client SUBSCRIBE 9 000100043232313200 |
| + | server SUBACK 3 9003000100 |
| + | server PUBLISH c 300c00043232313268656c6c6f0a |
| + | server DISCONNECT 0 e000 |
| + | </protocol> |
| + | </verify> |
| + | </testcase> |
| @@ -413,6 +413,25 @@ static int publish(FILE *dump, | |
| static char topic[MAX_TOPIC_LENGTH + 1]; | |
| + | static bool readall(curl_socket_t fd, unsigned char *buffer, size_t len) |
| + | { |
| + | size_t nread = 0; |
| + | |
| + | while(nread < len) { |
| + | ssize_t rc = sread(fd, buffer + nread, len - nread); |
| + | if(rc <= 0) { |
| + | if(rc < 0 && SOCKERRNO == SOCKEINTR && !got_exit_signal) |
| + | continue; |
| + | logmsg("READ %zd bytes [SHORT!]", rc); |
| + | return FALSE; |
| + | } |
| + | logmsg("READ %zd bytes", rc); |
| + | loghex(buffer + nread, rc); |
| + | nread += (size_t)rc; |
| + | } |
| + | return TRUE; |
| + | } |
| + | |
| static bool fixedheader(curl_socket_t fd, | |
| unsigned char *bytep, | |
| size_t *remaining_lengthp, | |
| @@ -421,23 +440,18 @@ static bool fixedheader(curl_socket_t fd, | |
| /* get the fixed header */ | |
| unsigned char buffer[10]; | |
| - | /* get the first two bytes */ |
| - | ssize_t rc = sread(fd, buffer, 2); |
| size_t i; | |
| - | if(rc < 2) { |
| - | logmsg("READ %zd bytes [SHORT!]", rc); |
| + | |
| + | /* get the first two bytes */ |
| + | if(!readall(fd, buffer, 2)) |
| return FALSE; /* fail */ | |
| - | } |
| - | logmsg("READ %zd bytes", rc); |
| - | loghex(buffer, rc); |
| *bytep = buffer[0]; | |
| /* if the length byte has the top bit set, get the next one too */ | |
| i = 1; | |
| while(buffer[i] & 0x80) { | |
| i++; | |
| - | rc = sread(fd, &buffer[i], 1); |
| - | if(rc != 1) { |
| + | if(!readall(fd, &buffer[i], 1)) { |
| logmsg("Remaining Length broken"); | |
| return FALSE; | |
| } | |
| @@ -513,14 +527,10 @@ static curl_socket_t mqttit(curl_socket_t fd) | |
| buffer = newbuffer; | |
| } | |
| - | if(remaining_length) { |
| - | /* reading variable header and payload into buffer */ |
| - | rc = sread(fd, buffer, remaining_length); |
| - | if(rc > 0) { |
| - | logmsg("READ %zd bytes", rc); |
| - | loghex(buffer, rc); |
| - | } |
| - | } |
| + | /* Read the complete variable header and payload. */ |
| + | if(!readall(fd, buffer, remaining_length)) |
| + | goto end; |
| + | rc = (ssize_t)remaining_length; |
| if(byte == MQTT_MSG_CONNECT) { | |
| logprotocol(FROM_CLIENT, "CONNECT", remaining_length, dump, buffer, rc); | |
| @@ -658,11 +668,9 @@ static curl_socket_t mqttit(curl_socket_t fd) | |
| #endif | |
| /* expect a disconnect here */ | |
| /* get the request */ | |
| - | rc = sread(fd, &buffer[0], 2); |
| - | |
| - | logmsg("READ %zd bytes [DISCONNECT]", rc); |
| - | loghex(buffer, rc); |
| - | logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, rc); |
| + | if(!readall(fd, buffer, 2)) |
| + | goto end; |
| + | logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, 2); |
| goto end; | |
| } | |
| else { | |
| @@ -80,6 +80,7 @@ TESTS_C = \ | |
| unit1674.c \ | |
| unit1675.c \ | |
| unit1676.c \ | |
| + | unit1693.c \ |
| unit1961.c \ | |
| unit1979.c \ | |
| unit1980.c \ | |
| @@ -0,0 +1,236 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, et al. |
| + | * |
| + | * This software is licensed as described in the file COPYING, which |
| + | * you should have received as part of this distribution. The terms |
| + | * are also available at https://curl.se/docs/copyright.html. |
| + | * |
| + | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| + | * copies of the Software, and permit persons to whom the Software is |
| + | * furnished to do so, under the terms of the COPYING file. |
| + | * |
| + | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| + | * KIND, either express or implied. |
| + | * |
| + | * SPDX-License-Identifier: curl |
| + | * |
| + | ***************************************************************************/ |
| + | #include "unitcheck.h" |
| + | #include "urldata.h" |
| + | #include "mqtt.h" |
| + | #include "select.h" |
| + | #include "url.h" |
| + | |
| + | #ifndef CURL_DISABLE_MQTT |
| + | |
| + | enum { |
| + | T1693_CONNECT_LEN = 26, |
| + | T1693_PUBLISH_LEN = 15, |
| + | T1693_DISCONNECT_LEN = 2, |
| + | T1693_PUBLISH_END = T1693_CONNECT_LEN + T1693_PUBLISH_LEN, |
| + | T1693_WIRE_LEN = T1693_PUBLISH_END + T1693_DISCONNECT_LEN |
| + | }; |
| + | |
| + | struct t1693_send { |
| + | size_t n; |
| + | CURLcode result; |
| + | }; |
| + | |
| + | struct t1693_ctx { |
| + | const struct t1693_send *sends; |
| + | size_t nsends; |
| + | size_t send_index; |
| + | size_t recv_index; |
| + | size_t written; |
| + | size_t pending; |
| + | unsigned char expected[T1693_WIRE_LEN]; |
| + | unsigned char output[T1693_WIRE_LEN]; |
| + | }; |
| + | |
| + | static CURLcode t1693_send(struct Curl_easy *data, int8_t sockindex, |
| + | const uint8_t *buf, size_t len, bool eos, |
| + | size_t *pnwritten) |
| + | { |
| + | struct t1693_ctx *ctx = data->set.private_data; |
| + | const struct t1693_send *send; |
| + | size_t remaining; |
| + | (void)sockindex; |
| + | (void)eos; |
| + | *pnwritten = 0; |
| + | |
| + | abort_unless(ctx->send_index < ctx->nsends, "unexpected send"); |
| + | send = &ctx->sends[ctx->send_index++]; |
| + | if(ctx->send_index == 1) { |
| + | /* The CONNECT client ID is random. Save the packet for retry checks. */ |
| + | abort_unless(len == T1693_CONNECT_LEN, "unexpected CONNECT size"); |
| + | memcpy(ctx->expected, buf, len); |
| + | fail_unless(buf[0] == 0x10 && buf[1] == 0x18, |
| + | "unexpected CONNECT header"); |
| + | } |
| + | |
| + | if(ctx->written < T1693_CONNECT_LEN) |
| + | remaining = T1693_CONNECT_LEN - ctx->written; |
| + | else if(ctx->written < T1693_PUBLISH_END) |
| + | remaining = T1693_PUBLISH_END - ctx->written; |
| + | else |
| + | remaining = sizeof(ctx->expected) - ctx->written; |
| + | abort_unless(len == remaining, "packet sent before previous packet drained"); |
| + | fail_unless(!memcmp(buf, ctx->expected + ctx->written, len), |
| + | "queued packet bytes changed"); |
| + | ctx->pending = len; |
| + | if(send->result) |
| + | return send->result; |
| + | |
| + | abort_unless(send->n <= len, "send script exceeds packet size"); |
| + | memcpy(ctx->output + ctx->written, buf, send->n); |
| + | ctx->written += send->n; |
| + | ctx->pending -= send->n; |
| + | *pnwritten = send->n; |
| + | return CURLE_OK; |
| + | |
| + | unit_test_abort: |
| + | return CURLE_SEND_ERROR; |
| + | } |
| + | |
| + | static CURLcode t1693_recv(struct Curl_easy *data, int8_t sockindex, |
| + | char *buf, size_t len, size_t *pnread) |
| + | { |
| + | static const unsigned char connack[] = { 0x20, 0x02, 0x00, 0x00 }; |
| + | struct t1693_ctx *ctx = data->set.private_data; |
| + | size_t n = sizeof(connack) - ctx->recv_index; |
| + | (void)sockindex; |
| + | *pnread = 0; |
| + | abort_unless(ctx->written == T1693_CONNECT_LEN, |
| + | "read while an outgoing packet is queued"); |
| + | abort_unless(n, "unexpected read after CONNACK"); |
| + | if(n > len) |
| + | n = len; |
| + | memcpy(buf, connack + ctx->recv_index, n); |
| + | ctx->recv_index += n; |
| + | *pnread = n; |
| + | return CURLE_OK; |
| + | |
| + | unit_test_abort: |
| + | return CURLE_RECV_ERROR; |
| + | } |
| + | |
| + | static void t1693_meta_dtor(void *p) |
| + | { |
| + | /* MQTT metadata supplies its own destructor. */ |
| + | (void)p; |
| + | } |
| + | |
| + | static void t1693_run(const struct t1693_send *sends, size_t nsends, |
| + | CURLcode expected_result) |
| + | { |
| + | static const unsigned char post[] = { |
| + | 0x30, 0x0d, 0x00, 0x04, 't', 'e', 's', 't', |
| + | 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0xe0, 0x00 |
| + | }; |
| + | struct t1693_ctx ctx; |
| + | struct Curl_easy *data = NULL; |
| + | struct connectdata *conn = NULL; |
| + | struct easy_pollset ps; |
| + | CURLcode result; |
| + | bool done = FALSE; |
| + | size_t i; |
| + | |
| + | Curl_pollset_init(&ps); |
| + | memset(&ctx, 0, sizeof(ctx)); |
| + | ctx.sends = sends; |
| + | ctx.nsends = nsends; |
| + | memcpy(ctx.expected + T1693_CONNECT_LEN, post, sizeof(post)); |
| + | |
| + | data = curl_easy_init(); |
| + | abort_unless(data, "easy handle allocation failed"); |
| + | conn = curlx_calloc(1, sizeof(*conn)); |
| + | abort_unless(conn, "connection allocation failed"); |
| + | Curl_hash_init(&conn->meta_hash, 23, CURL_HASH_TYPE_BYTES, t1693_meta_dtor); |
| + | data->conn = conn; |
| + | conn->sock[FIRSTSOCKET] = 42; |
| + | conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; |
| + | conn->send[FIRSTSOCKET] = t1693_send; |
| + | conn->recv[FIRSTSOCKET] = t1693_recv; |
| + | data->set.private_data = &ctx; |
| + | data->set.postfields = CURL_UNCONST("payload"); |
| + | data->state.httpreq = HTTPREQ_POST; |
| + | data->state.up.path = curlx_strdup("/test"); |
| + | abort_unless(data->state.up.path, "path allocation failed"); |
| + | result = Curl_protocol_mqtt.setup_connection(data, conn); |
| + | abort_unless(!result, "MQTT setup failed"); |
| + | |
| + | result = Curl_protocol_mqtt.do_it(data, &done); |
| + | for(i = 0; !result && !done && i < nsends + 4; ++i) { |
| + | Curl_pollset_reset(&ps); |
| + | result = Curl_protocol_mqtt.doing_pollset(data, &ps); |
| + | abort_unless(!result, "MQTT pollset failed"); |
| + | abort_unless(ps.n == 1 && ps.sockets[0] == 42, |
| + | "unexpected pollset socket"); |
| + | fail_unless(ps.actions[0] == (ctx.pending ? CURL_POLL_OUT : CURL_POLL_IN), |
| + | "polling the wrong direction for queued output"); |
| + | result = Curl_protocol_mqtt.doing(data, &done); |
| + | fail_unless(!done || ctx.written == sizeof(ctx.output), |
| + | "transfer completed with unsent packet bytes"); |
| + | } |
| + | fail_unless(result == expected_result, "unexpected transfer result"); |
| + | fail_unless(ctx.send_index == nsends, "not all scripted sends were used"); |
| + | fail_unless(!memcmp(ctx.output, ctx.expected, ctx.written), |
| + | "packets were not sent in order"); |
| + | if(!expected_result) { |
| + | fail_unless(done, "transfer did not complete"); |
| + | fail_unless(ctx.written == sizeof(ctx.output), "missing packet bytes"); |
| + | } |
| + | else |
| + | fail_unless(!done, "send failure marked the transfer complete"); |
| + | |
| + | unit_test_abort: |
| + | Curl_pollset_cleanup(&ps); |
| + | if(conn) |
| + | Curl_conn_free(data, conn); |
| + | if(data) { |
| + | data->conn = NULL; |
| + | curl_easy_cleanup(data); |
| + | } |
| + | } |
| + | |
| + | #endif /* CURL_DISABLE_MQTT */ |
| + | |
| + | static CURLcode test_unit1693(const char *arg) |
| + | { |
| + | UNITTEST_BEGIN_SIMPLE |
| + | |
| + | #ifndef CURL_DISABLE_MQTT |
| + | static const struct t1693_send full[] = { |
| + | { T1693_CONNECT_LEN, CURLE_OK }, { T1693_PUBLISH_LEN, CURLE_OK }, |
| + | { T1693_DISCONNECT_LEN, CURLE_OK } |
| + | }; |
| + | static const struct t1693_send partial[] = { |
| + | { 3, CURLE_OK }, { 0, CURLE_AGAIN }, { 4, CURLE_OK }, { 19, CURLE_OK }, |
| + | { 3, CURLE_OK }, { 4, CURLE_OK }, { 0, CURLE_AGAIN }, { 8, CURLE_OK }, |
| + | { 0, CURLE_AGAIN }, { 1, CURLE_OK }, { 0, CURLE_AGAIN }, { 1, CURLE_OK } |
| + | }; |
| + | static const struct t1693_send zero[] = { |
| + | { 0, CURLE_OK }, { T1693_CONNECT_LEN, CURLE_OK }, |
| + | { 0, CURLE_OK }, { T1693_PUBLISH_LEN, CURLE_OK }, |
| + | { 0, CURLE_OK }, { T1693_DISCONNECT_LEN, CURLE_OK } |
| + | }; |
| + | static const struct t1693_send send_error[] = { |
| + | { T1693_CONNECT_LEN, CURLE_OK }, { 4, CURLE_OK }, { 0, CURLE_SEND_ERROR } |
| + | }; |
| + | |
| + | abort_unless(!curl_global_init(CURL_GLOBAL_ALL), "global init failed"); |
| + | t1693_run(full, CURL_ARRAYSIZE(full), CURLE_OK); |
| + | t1693_run(partial, CURL_ARRAYSIZE(partial), CURLE_OK); |
| + | t1693_run(zero, CURL_ARRAYSIZE(zero), CURLE_OK); |
| + | t1693_run(send_error, CURL_ARRAYSIZE(send_error), CURLE_SEND_ERROR); |
| + | curl_global_cleanup(); |
| + | #endif |
| + | |
| + | UNITTEST_END_SIMPLE |
| + | } |