mirror of https://github.com/eclipse/paho.mqtt.c
604 lines
22 KiB
C
604 lines
22 KiB
C
/*******************************************************************************
|
|
* Copyright (c) 2026 IBM Corp. and Ian Craggs
|
|
*
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v2.0
|
|
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
|
*
|
|
* The Eclipse Public License is available at
|
|
* https://www.eclipse.org/legal/epl-2.0/
|
|
* and the Eclipse Distribution License is available at
|
|
* http://www.eclipse.org/org/documents/edl-v10.php.
|
|
*
|
|
* AI Disclosure: This file was partly AI-generated. The AI-generated
|
|
* portions are made available under CC0-1.0 and not subject to the
|
|
* project's licence. The human contributor has reviewed and verified
|
|
* that the code is correct.
|
|
*
|
|
* SPDX-License-Identifier: EPL-2.0 and CC0-1.0
|
|
*
|
|
* Contributors:
|
|
*. Ian Craggs - initial implementation and documentation
|
|
*******************************************************************************/
|
|
|
|
/**
|
|
* @file
|
|
* White-box unit tests for internal modules that don't need a live broker
|
|
* connection: Base64, Tree, Proxy (parsing only), MQTTProperties and utf-8.
|
|
* These call internal functions directly (not the public MQTTClient/MQTTAsync
|
|
* API), targeting error/edge paths that the broker-driven integration tests
|
|
* don't reach.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "MQTTAsync.h"
|
|
#include "Base64.h"
|
|
#include "Tree.h"
|
|
#include "Proxy.h"
|
|
#include "MQTTProperties.h"
|
|
#include "MQTTPacket.h"
|
|
#include "utf-8.h"
|
|
#include "Heap.h" /* redefines malloc/free as mymalloc/myfree, matching how Proxy.c allocates */
|
|
|
|
/* internal functions with external linkage that aren't declared in the public headers */
|
|
Node* TreeFindContentIndex(Tree* aTree, void* key, int index);
|
|
Node* TreeNextElementIndex(Tree* aTree, Node* curnode, int index);
|
|
void* TreeRemoveIndex(Tree* aTree, void* content, int index);
|
|
int MQTTProperty_write(char** pptr, MQTTProperty* prop);
|
|
int MQTTProperty_read(MQTTProperty* prop, char** pptr, char* enddata);
|
|
|
|
static unsigned int fails = 0u;
|
|
|
|
#define TEST_EXPECT(name, x) \
|
|
if (!(x)) { fprintf(stderr, "failed test: %s (%s:%d)\n", name, __FILE__, __LINE__); ++fails; }
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Base64 */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void test_base64(void)
|
|
{
|
|
char out[512];
|
|
b64_size_t r;
|
|
|
|
/* round trip, including edge lengths that leave 1 or 2 bytes of padding */
|
|
const char* inputs[] = { "", "p", "pa", "pah", "paho", "paho with websockets." };
|
|
int i;
|
|
|
|
for (i = 0; i < (int)(sizeof(inputs) / sizeof(inputs[0])); ++i)
|
|
{
|
|
char encoded[512];
|
|
char decoded[512];
|
|
b64_size_t inlen = (b64_size_t)strlen(inputs[i]);
|
|
b64_size_t enclen;
|
|
|
|
enclen = Base64_encode(encoded, sizeof(encoded), (const b64_data_t*)inputs[i], inlen);
|
|
TEST_EXPECT("base64 encode length matches Base64_encodeLength",
|
|
enclen == Base64_encodeLength((const b64_data_t*)inputs[i], inlen));
|
|
|
|
r = Base64_decode((b64_data_t*)decoded, sizeof(decoded), encoded, enclen);
|
|
TEST_EXPECT("base64 decode length matches Base64_decodeLength",
|
|
r == Base64_decodeLength(encoded, enclen));
|
|
TEST_EXPECT("base64 round trip content matches",
|
|
r == inlen && memcmp(decoded, inputs[i], inlen) == 0);
|
|
}
|
|
|
|
/* decode: input length not a multiple of 4 - too short to decode anything */
|
|
r = Base64_decode((b64_data_t*)out, sizeof(out), "cGE", 3);
|
|
TEST_EXPECT("base64 decode with in_len < 4 returns 0", r == 0);
|
|
|
|
/* decode: output buffer too small for the first byte only ("cGE=" -> "pa") */
|
|
r = Base64_decode((b64_data_t*)out, 1, "cGE=", 4);
|
|
TEST_EXPECT("base64 decode stops when out_len exhausted after 1st byte", r == 1);
|
|
|
|
/* decode: output buffer holds exactly 2 of the 3 decoded bytes ("cGFo" -> "pah") */
|
|
r = Base64_decode((b64_data_t*)out, 2, "cGFo", 4);
|
|
TEST_EXPECT("base64 decode stops when out_len exhausted after 2nd byte", r == 2);
|
|
|
|
/* decode: invalid character in the 3rd position of a quad */
|
|
r = Base64_decode((b64_data_t*)out, sizeof(out), "AB!D", 4);
|
|
TEST_EXPECT("base64 decode with invalid 3rd char stops at 1 byte", r == 1);
|
|
|
|
/* decode: invalid character in the 4th position of a quad */
|
|
r = Base64_decode((b64_data_t*)out, sizeof(out), "ABC!", 4);
|
|
TEST_EXPECT("base64 decode with invalid 4th char stops at 2 bytes", r == 2);
|
|
|
|
/* decodeLength / encodeLength edge cases (NULL / short inputs) */
|
|
TEST_EXPECT("base64 decodeLength NULL input", Base64_decodeLength(NULL, 4) == 3);
|
|
TEST_EXPECT("base64 decodeLength zero length", Base64_decodeLength("cGE=", 0) == 0);
|
|
TEST_EXPECT("base64 decodeLength length 1, no padding check on 2nd-last byte",
|
|
Base64_decodeLength("c", 1) == 0);
|
|
TEST_EXPECT("base64 decodeLength one padding char", Base64_decodeLength("cGE=", 4) == 2);
|
|
TEST_EXPECT("base64 decodeLength two padding chars", Base64_decodeLength("cA==", 4) == 1);
|
|
TEST_EXPECT("base64 decodeLength no padding", Base64_decodeLength("cGFo", 4) == 3);
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Tree */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void test_tree(void)
|
|
{
|
|
Tree* t;
|
|
Tree stackTree;
|
|
Node* n;
|
|
int values[] = { 5, 3, 8, 1, 4, 7, 9, 2, 6, 0 };
|
|
int i, prev, count;
|
|
const char* strs[] = { "banana", "apple", "cherry" };
|
|
|
|
/* direct compare function checks (note: these return -1 when the first arg is
|
|
* the *greater* one - an inverted convention relative to strcmp, but self-consistent
|
|
* for how the tree uses it) */
|
|
{
|
|
int a = 1, b = 2;
|
|
TEST_EXPECT("TreeIntCompare with a < b", TreeIntCompare(&a, &b, 0) == 1);
|
|
TEST_EXPECT("TreeIntCompare with a > b", TreeIntCompare(&b, &a, 0) == -1);
|
|
TEST_EXPECT("TreeIntCompare equal", TreeIntCompare(&a, &a, 0) == 0);
|
|
}
|
|
{
|
|
int a = 1, b = 2;
|
|
TEST_EXPECT("TreePtrCompare with a > b", TreePtrCompare(&a, &b, 0) == (&a > &b ? -1 : 1));
|
|
TEST_EXPECT("TreePtrCompare equal", TreePtrCompare(&a, &a, 0) == 0);
|
|
}
|
|
TEST_EXPECT("TreeStringCompare less", TreeStringCompare("apple", "banana", 0) < 0);
|
|
TEST_EXPECT("TreeStringCompare equal", TreeStringCompare("apple", "apple", 0) == 0);
|
|
TEST_EXPECT("TreeStringCompare greater", TreeStringCompare("banana", "apple", 0) > 0);
|
|
|
|
/* dynamically allocated tree, single index, iteration in sorted order */
|
|
t = TreeInitialize(TreeIntCompare);
|
|
TEST_EXPECT("TreeInitialize returns non-NULL", t != NULL);
|
|
|
|
for (i = 0; i < (int)(sizeof(values) / sizeof(values[0])); ++i)
|
|
TreeAdd(t, &values[i], sizeof(int));
|
|
TEST_EXPECT("Tree count after adds", t->count == (int)(sizeof(values) / sizeof(values[0])));
|
|
|
|
n = TreeFind(t, &values[0]);
|
|
TEST_EXPECT("TreeFind locates an added value", n != NULL && *(int*)(n->content) == values[0]);
|
|
|
|
n = NULL;
|
|
prev = -1;
|
|
count = 0;
|
|
while ((n = TreeNextElement(t, n)) != NULL)
|
|
{
|
|
int v = *(int*)(n->content);
|
|
TEST_EXPECT("TreeNextElement returns ascending order", v > prev);
|
|
prev = v;
|
|
++count;
|
|
}
|
|
TEST_EXPECT("TreeNextElement visited every node", count == (int)(sizeof(values) / sizeof(values[0])));
|
|
|
|
{
|
|
int key = 4;
|
|
void* removed = TreeRemove(t, &key);
|
|
TEST_EXPECT("TreeRemove finds and removes an existing key", removed != NULL);
|
|
n = TreeFind(t, &key);
|
|
TEST_EXPECT("TreeFind no longer finds removed key", n == NULL);
|
|
}
|
|
|
|
TreeFree(t);
|
|
|
|
/* stack-allocated tree (no malloc), second index, both indexes ordering the
|
|
* same string content (content type must be consistent across all indexes
|
|
* added via TreeAddIndex, since TreeAdd inserts into every index) */
|
|
TreeInitializeNoMalloc(&stackTree, TreeStringCompare);
|
|
TreeAddIndex(&stackTree, TreeStringCompare);
|
|
TEST_EXPECT("TreeAddIndex increments indexes", stackTree.indexes == 2);
|
|
|
|
for (i = 0; i < (int)(sizeof(strs) / sizeof(strs[0])); ++i)
|
|
TreeAdd(&stackTree, (void*)strs[i], strlen(strs[i]) + 1);
|
|
|
|
n = TreeFindIndex(&stackTree, (void*)"apple", 1);
|
|
TEST_EXPECT("TreeFindIndex on second index finds content",
|
|
n != NULL && strcmp((char*)n->content, "apple") == 0);
|
|
|
|
n = TreeFindContentIndex(&stackTree, (void*)"banana", 1);
|
|
TEST_EXPECT("TreeFindContentIndex on second index finds content",
|
|
n != NULL && strcmp((char*)n->content, "banana") == 0);
|
|
|
|
n = NULL;
|
|
count = 0;
|
|
while ((n = TreeNextElementIndex(&stackTree, n, 1)) != NULL)
|
|
++count;
|
|
TEST_EXPECT("TreeNextElementIndex walks all nodes on second index",
|
|
count == (int)(sizeof(strs) / sizeof(strs[0])));
|
|
|
|
{
|
|
void* removed = TreeRemoveIndex(&stackTree, (void*)"cherry", 1);
|
|
TEST_EXPECT("TreeRemoveIndex removes via second index", removed != NULL);
|
|
}
|
|
|
|
/* allow_duplicates: when set, adding a key that already compares equal to an
|
|
* existing one is silently skipped (unlike the default, which replaces the
|
|
* existing node's content in place). Use a fresh, single-index int tree. */
|
|
{
|
|
Tree dupTree;
|
|
int dupkey1 = 42, dupkey2 = 42;
|
|
void* addResult;
|
|
Node* found;
|
|
|
|
TreeInitializeNoMalloc(&dupTree, TreeIntCompare);
|
|
dupTree.allow_duplicates = 1;
|
|
|
|
TreeAdd(&dupTree, &dupkey1, sizeof(int));
|
|
addResult = TreeAdd(&dupTree, &dupkey2, sizeof(int));
|
|
|
|
TEST_EXPECT("allow_duplicates skips inserting a second equal key",
|
|
dupTree.count == 1 && addResult == NULL);
|
|
|
|
found = TreeFind(&dupTree, &dupkey2);
|
|
TEST_EXPECT("allow_duplicates leaves the original entry's content in place",
|
|
found != NULL && found->content == &dupkey1);
|
|
}
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Proxy (pure parsing functions only - no live connection) */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void test_proxy(void)
|
|
{
|
|
char no_proxy_buf[256];
|
|
|
|
strcpy(no_proxy_buf, "example.com");
|
|
TEST_EXPECT("Proxy_noProxy exact match returns 0 (don't use proxy)",
|
|
Proxy_noProxy("example.com", no_proxy_buf) == 0);
|
|
|
|
strcpy(no_proxy_buf, "example.com");
|
|
TEST_EXPECT("Proxy_noProxy suffix match returns 0",
|
|
Proxy_noProxy("sub.example.com", no_proxy_buf) == 0);
|
|
|
|
strcpy(no_proxy_buf, "example.com");
|
|
TEST_EXPECT("Proxy_noProxy non-matching host returns 1 (use proxy)",
|
|
Proxy_noProxy("notexample.com", no_proxy_buf) == 1);
|
|
|
|
strcpy(no_proxy_buf, "*");
|
|
TEST_EXPECT("Proxy_noProxy wildcard matches anything",
|
|
Proxy_noProxy("anything.example.org", no_proxy_buf) == 0);
|
|
|
|
strcpy(no_proxy_buf, "foo.com,example.com");
|
|
TEST_EXPECT("Proxy_noProxy matches second entry in comma list",
|
|
Proxy_noProxy("example.com", no_proxy_buf) == 0);
|
|
|
|
strcpy(no_proxy_buf, "foo.com,bar.com");
|
|
TEST_EXPECT("Proxy_noProxy with no match at all returns 1",
|
|
Proxy_noProxy("example.com", no_proxy_buf) == 1);
|
|
|
|
/* Proxy_setHTTPProxy: no auth */
|
|
{
|
|
char source[] = "http://myproxy.example.com:8080/";
|
|
char* dest = NULL;
|
|
char* auth_dest = NULL;
|
|
int rc = Proxy_setHTTPProxy(NULL, source, &dest, &auth_dest, "http://");
|
|
|
|
TEST_EXPECT("Proxy_setHTTPProxy without auth succeeds", rc == 0);
|
|
TEST_EXPECT("Proxy_setHTTPProxy strips the prefix",
|
|
dest != NULL && strcmp(dest, "myproxy.example.com:8080/") == 0);
|
|
TEST_EXPECT("Proxy_setHTTPProxy leaves auth NULL when none is given", auth_dest == NULL);
|
|
}
|
|
|
|
/* Proxy_setHTTPProxy: with user:pass auth, verify the encoded auth matches
|
|
* an independently computed Base64 encoding of "user:pass" */
|
|
{
|
|
char source[] = "http://user:pass@myproxy.example.com:8080/";
|
|
char* dest = NULL;
|
|
char* auth_dest = NULL;
|
|
char expected[64];
|
|
b64_size_t explen;
|
|
int rc;
|
|
|
|
explen = Base64_encode(expected, sizeof(expected), (const b64_data_t*)"user:pass", 9);
|
|
expected[explen] = '\0';
|
|
|
|
rc = Proxy_setHTTPProxy(NULL, source, &dest, &auth_dest, "http://");
|
|
|
|
TEST_EXPECT("Proxy_setHTTPProxy with auth succeeds", rc == 0);
|
|
TEST_EXPECT("Proxy_setHTTPProxy dest starts after the '@'",
|
|
dest != NULL && strcmp(dest, "myproxy.example.com:8080/") == 0);
|
|
TEST_EXPECT("Proxy_setHTTPProxy encodes auth as expected",
|
|
auth_dest != NULL && strcmp(auth_dest, expected) == 0);
|
|
free(auth_dest);
|
|
}
|
|
|
|
/* Proxy_setHTTPProxy: NULL source is a safe no-op */
|
|
{
|
|
char* dest = (char*)0x1; /* sentinel: should remain untouched */
|
|
char* auth_dest = NULL;
|
|
int rc = Proxy_setHTTPProxy(NULL, NULL, &dest, &auth_dest, "http://");
|
|
|
|
TEST_EXPECT("Proxy_setHTTPProxy with NULL source returns success", rc == 0);
|
|
TEST_EXPECT("Proxy_setHTTPProxy with NULL source doesn't touch dest",
|
|
dest == (char*)0x1);
|
|
}
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* utf-8 */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void test_utf8(void)
|
|
{
|
|
/* 1-byte ASCII */
|
|
TEST_EXPECT("utf8 1-byte ascii valid", UTF8_validate(1, "A") == 1);
|
|
|
|
/* 2-byte: (c) copyright sign, 0xC2 0xA9 */
|
|
TEST_EXPECT("utf8 2-byte valid", UTF8_validate(2, "\xC2\xA9") == 1);
|
|
|
|
/* 3-byte, each of the four sub-ranges in valid_ranges */
|
|
TEST_EXPECT("utf8 3-byte valid (E0 sub-range)", UTF8_validate(3, "\xE0\xA0\x80") == 1);
|
|
TEST_EXPECT("utf8 3-byte valid (E1-EC sub-range)", UTF8_validate(3, "\xE1\x80\x80") == 1);
|
|
TEST_EXPECT("utf8 3-byte valid (ED sub-range, excludes surrogates)",
|
|
UTF8_validate(3, "\xED\x80\x80") == 1);
|
|
TEST_EXPECT("utf8 3-byte valid (EE-EF sub-range)", UTF8_validate(3, "\xEE\x80\x80") == 1);
|
|
|
|
/* 4-byte, each of the three sub-ranges */
|
|
TEST_EXPECT("utf8 4-byte valid (F0 sub-range)", UTF8_validate(4, "\xF0\x90\x80\x80") == 1);
|
|
TEST_EXPECT("utf8 4-byte valid (F1-F3 sub-range)", UTF8_validate(4, "\xF1\x80\x80\x80") == 1);
|
|
TEST_EXPECT("utf8 4-byte valid (F4 sub-range)", UTF8_validate(4, "\xF4\x80\x80\x80") == 1);
|
|
|
|
/* truncated multi-byte sequences: not enough bytes for the declared length */
|
|
TEST_EXPECT("utf8 truncated 3-byte sequence is invalid", UTF8_validate(2, "\xE0\xA0") == 0);
|
|
TEST_EXPECT("utf8 truncated 4-byte sequence is invalid", UTF8_validate(3, "\xF0\x90\x80") == 0);
|
|
|
|
/* invalid continuation byte within a valid lead byte's range */
|
|
TEST_EXPECT("utf8 invalid 2-byte continuation is invalid", UTF8_validate(2, "\xC2\x20") == 0);
|
|
TEST_EXPECT("utf8 invalid 3-byte continuation is invalid", UTF8_validate(3, "\xE0\x20\x80") == 0);
|
|
|
|
/* whole-string validation, embedding a valid multi-byte character */
|
|
TEST_EXPECT("UTF8_validateString accepts a valid multi-byte string",
|
|
UTF8_validateString("caf\xC3\xA9") == 1);
|
|
TEST_EXPECT("UTF8_validateString rejects an invalid byte sequence",
|
|
UTF8_validateString("caf\xFF\xFF") == 0);
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* MQTTProperties */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void test_mqtt_properties(void)
|
|
{
|
|
MQTTProperties props = MQTTProperties_initializer;
|
|
MQTTProperty prop;
|
|
int rc;
|
|
|
|
/* invalid property identifier is rejected */
|
|
memset(&prop, 0, sizeof(prop));
|
|
prop.identifier = (enum MQTTPropertyCodes)9999;
|
|
rc = MQTTProperties_add(&props, &prop);
|
|
TEST_EXPECT("MQTTProperties_add rejects an unknown property id", rc == MQTT_INVALID_PROPERTY_ID);
|
|
|
|
/* BYTE-type property: write then read back */
|
|
{
|
|
char buf[16];
|
|
char* wptr = buf;
|
|
char* rptr = buf;
|
|
MQTTProperty byteProp;
|
|
MQTTProperty readProp;
|
|
|
|
memset(&byteProp, 0, sizeof(byteProp));
|
|
byteProp.identifier = MQTTPROPERTY_CODE_MAXIMUM_QOS;
|
|
byteProp.value.byte = 2;
|
|
|
|
MQTTProperty_write(&wptr, &byteProp);
|
|
rc = MQTTProperty_read(&readProp, &rptr, buf + sizeof(buf));
|
|
TEST_EXPECT("MQTTProperty_read of a BYTE property succeeds", rc > 0);
|
|
TEST_EXPECT("MQTTProperty_read of a BYTE property returns the value written",
|
|
readProp.identifier == MQTTPROPERTY_CODE_MAXIMUM_QOS && readProp.value.byte == 2);
|
|
}
|
|
|
|
/* truncated buffers: not enough data for each property type */
|
|
{
|
|
char buf[8];
|
|
char* pptr;
|
|
MQTTProperty readProp;
|
|
|
|
/* BYTE: identifier present, but no value byte */
|
|
pptr = buf;
|
|
buf[0] = (char)MQTTPROPERTY_CODE_MAXIMUM_QOS;
|
|
rc = MQTTProperty_read(&readProp, &pptr, buf + 1);
|
|
TEST_EXPECT("MQTTProperty_read truncated BYTE value fails", rc == -1);
|
|
|
|
/* TWO_BYTE_INTEGER: identifier present, only 1 of 2 value bytes */
|
|
pptr = buf;
|
|
buf[0] = (char)MQTTPROPERTY_CODE_TOPIC_ALIAS;
|
|
buf[1] = 0;
|
|
rc = MQTTProperty_read(&readProp, &pptr, buf + 2);
|
|
TEST_EXPECT("MQTTProperty_read truncated TWO_BYTE_INTEGER fails", rc == -1);
|
|
|
|
/* FOUR_BYTE_INTEGER: identifier present, only 2 of 4 value bytes */
|
|
pptr = buf;
|
|
buf[0] = (char)MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL;
|
|
buf[1] = 0;
|
|
buf[2] = 0;
|
|
rc = MQTTProperty_read(&readProp, &pptr, buf + 3);
|
|
TEST_EXPECT("MQTTProperty_read truncated FOUR_BYTE_INTEGER fails", rc == -1);
|
|
|
|
/* UTF-8 string: identifier present, length prefix says 5 bytes but none follow */
|
|
pptr = buf;
|
|
buf[0] = (char)MQTTPROPERTY_CODE_CONTENT_TYPE;
|
|
buf[1] = 0;
|
|
buf[2] = 5;
|
|
rc = MQTTProperty_read(&readProp, &pptr, buf + 3);
|
|
TEST_EXPECT("MQTTProperty_read truncated UTF-8 string fails", rc == -1);
|
|
|
|
/* just the 1-byte identifier, nothing else in the buffer at all */
|
|
pptr = buf;
|
|
buf[0] = (char)MQTTPROPERTY_CODE_MAXIMUM_QOS;
|
|
rc = MQTTProperty_read(&readProp, &pptr, buf);
|
|
TEST_EXPECT("MQTTProperty_read with zero remaining data fails", rc == -1);
|
|
}
|
|
|
|
/* MQTTProperties_read: declared remaining length longer than the buffer supplied */
|
|
{
|
|
char buf[8];
|
|
char* pptr = buf;
|
|
MQTTProperties readProps = MQTTProperties_initializer;
|
|
|
|
buf[0] = 100; /* VBI: claims 100 bytes of properties follow, buffer is much shorter */
|
|
rc = MQTTProperties_read(&readProps, &pptr, buf + 1);
|
|
TEST_EXPECT("MQTTProperties_read with an over-long declared length fails",
|
|
rc == MQTTPACKET_BUFFER_TOO_SHORT);
|
|
}
|
|
|
|
/* MQTTProperties_read: more than 10 properties, to exercise array growth (realloc) */
|
|
{
|
|
char buf[256];
|
|
char* wptr;
|
|
char* pptr;
|
|
MQTTProperties writeProps = MQTTProperties_initializer;
|
|
MQTTProperties readProps = MQTTProperties_initializer;
|
|
int i;
|
|
int numProps = 15;
|
|
|
|
for (i = 0; i < numProps; ++i)
|
|
{
|
|
MQTTProperty p;
|
|
memset(&p, 0, sizeof(p));
|
|
p.identifier = MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIER;
|
|
p.value.integer4 = (unsigned int)(i + 1);
|
|
rc = MQTTProperties_add(&writeProps, &p);
|
|
TEST_EXPECT("MQTTProperties_add succeeds while building the growth test list", rc == 0);
|
|
}
|
|
|
|
wptr = buf;
|
|
rc = MQTTProperties_write(&wptr, &writeProps);
|
|
TEST_EXPECT("MQTTProperties_write of >10 properties succeeds", rc > 0);
|
|
|
|
/* MQTTProperties_read decodes its own leading VBI length prefix, so pptr
|
|
* must point at the very start of the buffer written by MQTTProperties_write */
|
|
pptr = buf;
|
|
rc = MQTTProperties_read(&readProps, &pptr, buf + sizeof(buf));
|
|
TEST_EXPECT("MQTTProperties_read of >10 properties succeeds", rc == 1);
|
|
TEST_EXPECT("MQTTProperties_read grows the array to hold all properties",
|
|
readProps.count == numProps && readProps.max_count >= numProps);
|
|
|
|
MQTTProperties_free(&writeProps);
|
|
MQTTProperties_free(&readProps);
|
|
}
|
|
|
|
/* MQTTProperties_read: a corrupt property (unknown identifier) mid-buffer -> MQTTPACKET_BAD */
|
|
{
|
|
char buf[8];
|
|
char* pptr = buf;
|
|
MQTTProperties readProps = MQTTProperties_initializer;
|
|
|
|
buf[0] = 1; /* VBI: 1 byte of properties follows */
|
|
buf[1] = (char)0x7F; /* an identifier that isn't in the known property table */
|
|
rc = MQTTProperties_read(&readProps, &pptr, buf + 2);
|
|
TEST_EXPECT("MQTTProperties_read with an unknown property id fails", rc == MQTTPACKET_BAD);
|
|
}
|
|
|
|
/* MQTTProperties_copy: source list has a manually-injected invalid identifier,
|
|
* which should be skipped/logged rather than crashing the copy */
|
|
{
|
|
MQTTProperties badProps = MQTTProperties_initializer;
|
|
MQTTProperties copied;
|
|
MQTTProperty p;
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
p.identifier = MQTTPROPERTY_CODE_MAXIMUM_QOS;
|
|
p.value.byte = 1;
|
|
MQTTProperties_add(&badProps, &p);
|
|
|
|
/* bypass validation: hand-corrupt the stored identifier after adding it */
|
|
badProps.array[0].identifier = (enum MQTTPropertyCodes)9999;
|
|
|
|
copied = MQTTProperties_copy(&badProps);
|
|
TEST_EXPECT("MQTTProperties_copy with a corrupt source entry doesn't crash and copies nothing",
|
|
copied.count == 0);
|
|
|
|
badProps.array[0].identifier = MQTTPROPERTY_CODE_MAXIMUM_QOS; /* restore so free() is safe */
|
|
MQTTProperties_free(&badProps);
|
|
MQTTProperties_free(&copied);
|
|
}
|
|
|
|
/* MQTTProperties_getNumericValueAt: BYTE type, and the "unsupported type" default */
|
|
{
|
|
MQTTProperties numProps = MQTTProperties_initializer;
|
|
MQTTProperty p;
|
|
int64_t v;
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
p.identifier = MQTTPROPERTY_CODE_MAXIMUM_QOS; /* BYTE type */
|
|
p.value.byte = 7;
|
|
MQTTProperties_add(&numProps, &p);
|
|
|
|
v = MQTTProperties_getNumericValue(&numProps, MQTTPROPERTY_CODE_MAXIMUM_QOS);
|
|
TEST_EXPECT("MQTTProperties_getNumericValue reads a BYTE property", v == 7);
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
p.identifier = MQTTPROPERTY_CODE_CONTENT_TYPE; /* UTF-8 string: unsupported for numeric read */
|
|
p.value.data.data = (char*)"text/plain";
|
|
p.value.data.len = (int)strlen("text/plain");
|
|
MQTTProperties_add(&numProps, &p);
|
|
|
|
v = MQTTProperties_getNumericValue(&numProps, MQTTPROPERTY_CODE_CONTENT_TYPE);
|
|
TEST_EXPECT("MQTTProperties_getNumericValue on a non-numeric type returns the default", v == -999999);
|
|
|
|
MQTTProperties_free(&numProps);
|
|
}
|
|
|
|
/* MQTTProperties_getPropertyAt: index > 0 to find the second occurrence */
|
|
{
|
|
MQTTProperties userProps = MQTTProperties_initializer;
|
|
MQTTProperty p;
|
|
MQTTProperty* found;
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
p.identifier = MQTTPROPERTY_CODE_USER_PROPERTY;
|
|
p.value.data.data = (char*)"k1";
|
|
p.value.data.len = 2;
|
|
p.value.value.data = (char*)"v1";
|
|
p.value.value.len = 2;
|
|
MQTTProperties_add(&userProps, &p);
|
|
|
|
p.value.data.data = (char*)"k2";
|
|
p.value.value.data = (char*)"v2";
|
|
MQTTProperties_add(&userProps, &p);
|
|
|
|
found = MQTTProperties_getPropertyAt(&userProps, MQTTPROPERTY_CODE_USER_PROPERTY, 1);
|
|
TEST_EXPECT("MQTTProperties_getPropertyAt(index=1) finds the second occurrence",
|
|
found != NULL && strncmp(found->value.data.data, "k2", 2) == 0);
|
|
|
|
MQTTProperties_free(&userProps);
|
|
}
|
|
|
|
MQTTProperties_free(&props);
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
/* initialize heap tracking / mutexes used internally by these modules,
|
|
* without needing an actual network connection. MQTTAsync_global_init()
|
|
* alone isn't enough: Heap_initialize() is normally only called lazily from
|
|
* MQTTAsync_createWithOptions, so it's called explicitly here instead. */
|
|
MQTTAsync_global_init(NULL);
|
|
#if !defined(HIGH_PERFORMANCE)
|
|
Heap_initialize();
|
|
#endif
|
|
|
|
test_base64();
|
|
test_tree();
|
|
test_proxy();
|
|
test_utf8();
|
|
test_mqtt_properties();
|
|
|
|
if (fails)
|
|
printf("%u tests failed\n", fails);
|
|
else
|
|
printf("all tests passed\n");
|
|
|
|
return (int)fails;
|
|
}
|