Skip to content

Commit ddda148

Browse files
committed
Fixes from review
1 parent 958e355 commit ddda148

4 files changed

Lines changed: 90 additions & 30 deletions

File tree

AGENTS.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,30 @@ Every shell command's exit code must be checked.
166166
Never proceed after a silent failure.
167167
A command that failed and was ignored is not a completed step.
168168

169-
### Test vector discipline
170-
Never derive test vectors from the code under test. Acceptable oracles:
171-
- OpenSSL (`openssl kdf`, `openssl enc`, `openssl pkcs8 ... | sha256sum`)
172-
- pyca/cryptography (`pkcs12.load_key_and_certificates`, `private_bytes(DER, PKCS8, NoEncryption)`)
173-
- Bouncy Castle test vectors
174-
Compute once, hardcode as `hex!(...)` literals or committed binary fixtures. Tests must be fully offline.
175-
For PKCS12KDF: use `hexpass:` (BMP/UTF-16BE + null terminator), NOT `pass:` (raw ASCII).
169+
## MQTT Specification Discipline
170+
Wire format and protocol behavior are governed by the published MQTT specifications. Treat the spec as the source of truth, not the code.
171+
172+
Relevant specifications:
173+
- MQTT v3.1.1 — OASIS Standard (`mqtt-v3.1.1-os`)
174+
- MQTT v5.0 — OASIS Standard (`mqtt-v5.0`)
175+
- MQTT-SN v1.2 — OASIS
176+
177+
When implementing or testing a normative requirement, cite it in a comment so reviewers can verify against the spec:
178+
- MQTT v3.1.1 / v5.0: bracketed conformance identifiers, e.g. `[MQTT-3.8.1-1]`, `[MQTT-2.3.1-1]`.
179+
- When a rule has no bracketed identifier, reference the section number, e.g. `MQTT 5.0 section 3.15.2`.
180+
- MQTT-SN v1.2: `MQTT-SN 1.2 section X.Y`.
181+
182+
Match the version scope of the change. MQTT v5.0 adds packets and fields (AUTH, Reason Codes, Properties) that do not exist in v3.1.1; do not apply a v5-only rule to v3.1.1 code paths or vice versa. Guard v5-only logic with `WOLFMQTT_V5` and v3.1.1-only logic accordingly.
183+
184+
## Test Oracle Discipline
185+
Do not use the code under test as its own oracle for wire-format behavior. For encoder or decoder tests, use one of:
186+
- A hand-constructed byte sequence that matches the spec wire format, built by reading the relevant section (not captured from encoder output). Comment the byte layout so the fixture is auditable.
187+
- Values from the spec's worked examples or conformance annex.
188+
- A cross-check against an independent implementation (e.g. mosquitto) captured once and committed as a fixed byte array.
189+
190+
Roundtrip tests (encode then decode, or vice versa) are acceptable for regression and structural coverage, but they cannot be the sole oracle for a wire-format rule — a bug present in both encoder and decoder will still roundtrip. Pair roundtrip coverage with at least one independent fixture per rule.
191+
192+
Tests must be fully offline and must not fetch vectors from the network at runtime.
176193

177194
## Dependencies
178195

scripts/broker.test

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,13 @@ if ./$sub_bin -h 2>&1 | grep -q "Max packet size"; then
327327
fi
328328
if [ "$v5_active" = "yes" ]; then
329329
LO_BOUND=10
330-
HI_BOUND=13
330+
HI_BOUND=14
331331
else
332332
LO_BOUND=5
333-
HI_BOUND=8
333+
HI_BOUND=9
334334
fi
335335
rm -f "${TMP_DIR}/t6b_watcher.ready" "${TMP_DIR}/t6b_client.ready"
336+
t6b_setup_ok=1
336337
timeout 30 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \
337338
-i "t6b_watcher" -R "${TMP_DIR}/t6b_watcher.ready" \
338339
>"${TMP_DIR}/t6b_sub.log" 2>&1 &
@@ -341,6 +342,7 @@ TEST_PIDS+=($T6B_WATCHER_PID)
341342
if ! wait_for_file "${TMP_DIR}/t6b_watcher.ready" 5; then
342343
echo "FAIL: Keep-alive timeout window (watcher did not become ready)"
343344
FAIL=1
345+
t6b_setup_ok=0
344346
fi
345347
./$sub_bin -T -h 127.0.0.1 -p $port -n "test/ka2_trigger" -i "ka2_client" -l -k 4 \
346348
-R "${TMP_DIR}/t6b_client.ready" >"${TMP_DIR}/t6b_client.log" 2>&1 &
@@ -349,31 +351,36 @@ TEST_PIDS+=($T6B_CLIENT_PID)
349351
if ! wait_for_file "${TMP_DIR}/t6b_client.ready" 5; then
350352
echo "FAIL: Keep-alive timeout window (client did not become ready)"
351353
FAIL=1
354+
t6b_setup_ok=0
355+
fi
356+
if [ $t6b_setup_ok -eq 1 ]; then
357+
kill -STOP $T6B_CLIENT_PID 2>/dev/null
358+
T6B_T_START=$(date +%s)
359+
T6B_ELAPSED=-1
360+
T6B_DEADLINE=$((T6B_T_START + HI_BOUND + 5))
361+
while [ $(date +%s) -lt $T6B_DEADLINE ]; do
362+
if grep -q "ka2_client" "${TMP_DIR}/t6b_sub.log" 2>/dev/null; then
363+
T6B_ELAPSED=$(($(date +%s) - T6B_T_START))
364+
break
365+
fi
366+
sleep 0.1
367+
done
352368
fi
353-
T6B_T_START=$(date +%s)
354-
kill -STOP $T6B_CLIENT_PID 2>/dev/null
355-
T6B_ELAPSED=-1
356-
T6B_DEADLINE=$((T6B_T_START + HI_BOUND + 5))
357-
while [ $(date +%s) -lt $T6B_DEADLINE ]; do
358-
if grep -q "ka2_client" "${TMP_DIR}/t6b_sub.log" 2>/dev/null; then
359-
T6B_ELAPSED=$(($(date +%s) - T6B_T_START))
360-
break
361-
fi
362-
sleep 0.1
363-
done
364369
kill -9 $T6B_CLIENT_PID 2>/dev/null
365370
wait $T6B_CLIENT_PID 2>/dev/null || true
366371
kill $T6B_WATCHER_PID 2>/dev/null
367372
wait $T6B_WATCHER_PID 2>/dev/null || true
368373
TEST_PIDS=()
369-
if [ $T6B_ELAPSED -lt 0 ]; then
370-
echo "FAIL: Keep-alive timeout window (no LWT within $((HI_BOUND + 5))s)"
371-
FAIL=1
372-
elif [ $T6B_ELAPSED -lt $LO_BOUND ] || [ $T6B_ELAPSED -gt $HI_BOUND ]; then
373-
echo "FAIL: Keep-alive timeout window (elapsed=${T6B_ELAPSED}s, expected ${LO_BOUND}-${HI_BOUND}s)"
374-
FAIL=1
375-
else
376-
echo "PASS: Keep-alive timeout window (elapsed=${T6B_ELAPSED}s, expected ${LO_BOUND}-${HI_BOUND}s)"
374+
if [ $t6b_setup_ok -eq 1 ]; then
375+
if [ $T6B_ELAPSED -lt 0 ]; then
376+
echo "FAIL: Keep-alive timeout window (no LWT within $((HI_BOUND + 5))s)"
377+
FAIL=1
378+
elif [ $T6B_ELAPSED -lt $LO_BOUND ] || [ $T6B_ELAPSED -gt $HI_BOUND ]; then
379+
echo "FAIL: Keep-alive timeout window (elapsed=${T6B_ELAPSED}s, expected ${LO_BOUND}-${HI_BOUND}s)"
380+
FAIL=1
381+
else
382+
echo "PASS: Keep-alive timeout window (elapsed=${T6B_ELAPSED}s, expected ${LO_BOUND}-${HI_BOUND}s)"
383+
fi
377384
fi
378385
fi # has_will
379386

src/mqtt_packet.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2760,7 +2760,7 @@ int MqttDecode_Auth(byte *rx_buf, int rx_buf_len, MqttAuth *auth)
27602760
}
27612761
}
27622762
else {
2763-
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER);
2763+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA);
27642764
}
27652765
}
27662766
else {

tests/test_mqtt_packet.c

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,41 @@ TEST(encode_subscribe_topic_filter_oversized_rejected)
804804
ASSERT_TRUE(rc < 0);
805805
}
806806

807+
/* when multiple topics are supplied and a later one is oversized,
808+
* the encoder must still reject — the length-validation loop covers every
809+
* entry, not just the first. */
810+
TEST(encode_subscribe_topic_filter_oversized_second_rejected)
811+
{
812+
const int str_len = 0x10000;
813+
const int buf_len = str_len + 128;
814+
byte *tx_buf = (byte*)WOLFMQTT_MALLOC(buf_len);
815+
char *filter = (char*)WOLFMQTT_MALLOC(str_len + 1);
816+
MqttSubscribe sub;
817+
MqttTopic topics[2];
818+
int rc;
819+
820+
if (tx_buf == NULL || filter == NULL) {
821+
WOLFMQTT_FREE(tx_buf);
822+
WOLFMQTT_FREE(filter);
823+
FAIL("allocation failed");
824+
}
825+
XMEMSET(filter, 'B', str_len);
826+
filter[str_len] = '\0';
827+
828+
XMEMSET(&sub, 0, sizeof(sub));
829+
XMEMSET(topics, 0, sizeof(topics));
830+
topics[0].topic_filter = "ok/topic";
831+
topics[1].topic_filter = filter;
832+
sub.topics = topics;
833+
sub.topic_count = 2;
834+
sub.packet_id = 1;
835+
rc = MqttEncode_Subscribe(tx_buf, buf_len, &sub);
836+
837+
WOLFMQTT_FREE(filter);
838+
WOLFMQTT_FREE(tx_buf);
839+
ASSERT_TRUE(rc < 0);
840+
}
841+
807842
/* [MQTT-3.8.1-1] SUBSCRIBE fixed header reserved flags MUST equal 0b0010,
808843
* which encodes QoS 1. Verifies the QoS 1 bit directly via
809844
* MQTT_PACKET_FLAGS_GET_QOS so a mutation of MQTT_QOS_1 to MQTT_QOS_0 in
@@ -2027,7 +2062,7 @@ TEST(auth_v5_invalid_reason_code_rejected)
20272062

20282063
XMEMSET(&dec, 0, sizeof(dec));
20292064
dec_len = MqttDecode_Auth(buf, 4, &dec);
2030-
ASSERT_TRUE(dec_len < 0);
2065+
ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, dec_len);
20312066
}
20322067
#endif /* WOLFMQTT_V5 */
20332068

@@ -2109,6 +2144,7 @@ void run_mqtt_packet_tests(void)
21092144
RUN_TEST(encode_subscribe_options_byte_qos1);
21102145
RUN_TEST(encode_subscribe_options_byte_qos2);
21112146
RUN_TEST(encode_subscribe_topic_filter_oversized_rejected);
2147+
RUN_TEST(encode_subscribe_topic_filter_oversized_second_rejected);
21122148
RUN_TEST(encode_subscribe_has_qos1_flag);
21132149

21142150
/* MqttEncode_Unsubscribe */

0 commit comments

Comments
 (0)