Zero-Length ClientId Unique Assignment Is Incomplete
Summary
This document shows that wolfMQTT's MQTT 3.1.1 handling of accepted zero-length ClientIds is incomplete. The broker may accept an empty ClientId without assigning a unique replacement in the MQTT 3.1.1 path, while the generated ClientId logic is tied to MQTT v5-specific conditions and does not uniformly enforce the required CleanSession=1 constraint.
English Standard Text
The relevant MQTT 3.1.1 rules are in Section 3.1.3 CONNECT Payload, Client Identifier.
Original English text with context:
The Client Identifier (ClientId) identifies the Client to the Server.
Each Client connecting to the Server has a unique ClientId.
The ClientId MUST be used by Clients and by Servers to identify state that
they hold relating to this MQTT Session between the Client and the Server
[MQTT-3.1.3-2].
The special zero-length ClientId rule is:
A Server MAY allow a Client to supply a ClientId that has a length of zero
bytes. However if it does so the Server MUST treat this as a special case and
assign a unique ClientId to that Client. It MUST then process the CONNECT
packet as if the Client had provided that unique ClientId [MQTT-3.1.3-6].
The CleanSession linkage rules are:
If the Client supplies a zero-byte ClientId, the Client MUST also set
CleanSession to 1 [MQTT-3.1.3-7].
If the Client supplies a zero-byte ClientId with CleanSession set to 0,
the Server MUST respond to the CONNECT Packet with a CONNACK return code
0x02 (Identifier rejected) and then close the Network Connection
[MQTT-3.1.3-8].
Meaning in this issue:
- A Server is not required to allow a zero-length ClientId. That part is
MAY.
- If the Server does allow it, assigning a unique ClientId is
MUST.
- After assignment, the Server must process the connection as if the Client had supplied that unique ID.
- For MQTT 3.1.1, zero-length ClientId is only valid with
CleanSession=1.
- Zero-length ClientId with
CleanSession=0 must be rejected with CONNACK 0x02 and the connection must be closed.
Expected MQTT 3.1.1 Behavior
For a MQTT 3.1.1 CONNECT packet with a zero-length ClientId:
| ClientId length |
CleanSession |
Server policy |
Expected behavior |
| 0 |
1 |
Server does not allow zero-length ClientId |
reject with CONNACK 0x02, then close |
| 0 |
1 |
Server allows zero-length ClientId |
assign a unique ClientId and process CONNECT as if that ID was supplied |
| 0 |
0 |
any policy |
reject with CONNACK 0x02, then close |
The key point is that accepting zero-length ClientId without assigning a unique ClientId is not one of the allowed MQTT 3.1.1 outcomes.
Code Description
1. CONNECT decoding accepts the ClientId field as a length-prefixed string
File: wolfMQTT-master/src/mqtt_packet.c:1038
tmp = MqttDecode_String(rx_payload, &mc_connect->client_id, NULL,
(word32)(rx_buf_len - (rx_payload - rx_buf)));
if (tmp < 0) {
return tmp;
}
MqttDecode_Connect() decodes the ClientId but does not enforce the zero-length special-case rules. It does not reject ClientId length == 0 && CleanSession == 0, and it does not assign a unique ClientId.
2. Broker stores the decoded ClientId length, including zero length
File: wolfMQTT-master/src/mqtt_broker.c:2708
if (mc.client_id) {
word16 id_len = 0;
if (MqttDecode_Num((byte*)mc.client_id - MQTT_DATA_LEN_SIZE,
&id_len, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) {
...
BROKER_STORE_STR(bc->client_id, mc.client_id, id_len,
BROKER_MAX_CLIENT_ID_LEN);
}
}
For a zero-length ClientId, id_len is 0. The broker stores that value and continues. There is no MQTT 3.1.1 branch here that changes an empty ClientId into a generated unique ID.
3. Empty ClientId skips normal uniqueness/session handling
File: wolfMQTT-master/src/mqtt_broker.c:2743
/* Client ID uniqueness and clean session handling */
bc->clean_session = mc.clean_session;
if (BROKER_STR_VALID(bc->client_id)) {
BrokerClient* old;
...
}
The uniqueness and existing-session logic is guarded by BROKER_STR_VALID(bc->client_id). A zero-length ClientId is not a valid non-empty string, so this block is skipped. That means the accepted MQTT 3.1.1 empty-ClientId path is not processed as a generated unique ClientId.
4. Default CONNACK state accepts the connection
File: wolfMQTT-master/src/mqtt_broker.c:2917
ack.flags = 0;
ack.return_code = MQTT_CONNECT_ACK_CODE_ACCEPTED;
File: wolfMQTT-master/src/mqtt_broker.c:3023
rc = MqttEncode_ConnectAck(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), &ack);
Unless another branch changes ack.return_code, the broker sends CONNACK success. The zero-length MQTT 3.1.1 ClientId path does not set MQTT_CONNECT_ACK_CODE_REFUSED_ID and does not assign a unique ClientId before this point.
5. Unique assignment exists only in a MQTT v5 conditional branch
File: wolfMQTT-master/src/mqtt_broker.c:2966
#ifdef WOLFMQTT_V5
if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 &&
ack.return_code == MQTT_CONNECT_ACK_CODE_ACCEPTED) {
MqttProp* prop;
/* If client sent empty client ID, generate one and inform client */
if (!BROKER_STR_VALID(bc->client_id)) {
char auto_id[32];
int id_len = XSNPRINTF(auto_id, (int)sizeof(auto_id),
"auto-%04x", broker->next_packet_id++);
File: wolfMQTT-master/src/mqtt_broker.c:2979
if (id_len > 0) {
BROKER_STORE_STR(bc->client_id, auto_id, (word16)id_len,
BROKER_MAX_CLIENT_ID_LEN);
}
File: wolfMQTT-master/src/mqtt_broker.c:2983
if (BROKER_STR_VALID(bc->client_id)) {
prop = MqttProps_Add(&ack.props);
if (prop != NULL) {
prop->type = MQTT_PROP_ASSIGNED_CLIENT_ID;
prop->data_str.str = bc->client_id;
prop->data_str.len = (word16)XSTRLEN(bc->client_id);
}
}
This assignment is conditional on:
WOLFMQTT_V5 being compiled;
- the connection protocol level being MQTT v5 or later;
- the CONNACK return code still being accepted;
- the stored
bc->client_id being empty.
It does not apply to MQTT 3.1.1 accepted CONNECT packets.
Dynamic Test Evidence
The existing raw CONNECT test sends zero-length ClientId packets.
Observed MQTT 3.1.1 behavior:
mqtt311_emptyid_clean1 -> CONNACK 20020000, return_code=0, connection remains alive
mqtt311_emptyid_clean0 -> CONNACK 20020000, return_code=0, connection remains alive
Interpretation:
mqtt311_emptyid_clean1: the broker accepts zero-length ClientId, but the MQTT 3.1.1 path does not assign a visible or internally non-empty unique ClientId before normal processing.
mqtt311_emptyid_clean0: the broker accepts a combination that MQTT 3.1.1 requires it to reject with 0x02 and close.
Observed MQTT v5 behavior:
mqtt5_emptyid_clean1 -> accepted and assigned client id such as auto-xxxx
mqtt5_emptyid_clean0 -> accepted and assigned client id such as auto-xxxx
This confirms the issue: unique assignment exists in a MQTT v5 accepted branch, but it is not a uniform MQTT 3.1.1 behavior and is not bound to the required CleanSession=1 condition.
Issue Mapping
| Issue aspect |
Why it is real |
| MQTT 3.1.1 assignment path |
The code has a generated ClientId path, but it is under WOLFMQTT_V5 and protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5. MQTT 3.1.1 empty ClientId acceptance does not use this path. |
| Conditional assignment logic |
The assignment logic is conditional on build flags, protocol level, accepted return code, and empty stored ClientId. It is not a single MQTT 3.1.1 special-case branch that handles every accepted zero-length ClientId. |
Inconsistency Reason
MQTT 3.1.1 gives the Server a choice: it may reject a zero-length ClientId, or it may allow it. But once wolfMQTT accepts a zero-length MQTT 3.1.1 ClientId, the standard requires the Server to assign a unique ClientId and process the CONNECT as if that unique ClientId had been supplied.
wolfMQTT does not implement that MQTT 3.1.1 special case uniformly:
- The MQTT 3.1.1 path can send CONNACK success for an empty ClientId.
- The normal ClientId uniqueness block is skipped because the stored ClientId remains empty.
- The only generated-ID branch is MQTT v5 specific.
- The generated-ID branch is also not guarded by
CleanSession=1, so it can assign an ID even for invalid empty ClientId + CleanSession=0 cases in the MQTT v5 path.
Therefore, MQTT 3.1.1 zero-length ClientId handling is incomplete: accepted empty ClientIds are not consistently assigned unique IDs, and invalid CleanSession=0 combinations are not rejected.
Conclusion
For MQTT 3.1.1, the unique-assignment behavior should be treated as part of the zero-length ClientId compliance gap:
wolfMQTT accepts zero-length ClientId in MQTT 3.1.1, but the required unique
ClientId assignment is not implemented in the MQTT 3.1.1 accepted path.
The only generated-ID logic is conditional and MQTT v5-specific.
These issues are closely related to the zero-length ClientId / CleanSession validation gap, but they describe a different part of the same standard rule: not only must CleanSession=0 be rejected, an accepted zero-length ClientId must also receive a unique ClientId.
Zero-Length ClientId Unique Assignment Is Incomplete
Summary
This document shows that wolfMQTT's MQTT 3.1.1 handling of accepted zero-length ClientIds is incomplete. The broker may accept an empty ClientId without assigning a unique replacement in the MQTT 3.1.1 path, while the generated ClientId logic is tied to MQTT v5-specific conditions and does not uniformly enforce the required
CleanSession=1constraint.English Standard Text
The relevant MQTT 3.1.1 rules are in Section
3.1.3 CONNECT Payload,Client Identifier.Original English text with context:
The special zero-length ClientId rule is:
The CleanSession linkage rules are:
Meaning in this issue:
MAY.MUST.CleanSession=1.CleanSession=0must be rejected with CONNACK0x02and the connection must be closed.Expected MQTT 3.1.1 Behavior
For a MQTT 3.1.1 CONNECT packet with a zero-length ClientId:
0x02, then close0x02, then closeThe key point is that accepting zero-length ClientId without assigning a unique ClientId is not one of the allowed MQTT 3.1.1 outcomes.
Code Description
1. CONNECT decoding accepts the ClientId field as a length-prefixed string
File:
wolfMQTT-master/src/mqtt_packet.c:1038MqttDecode_Connect()decodes the ClientId but does not enforce the zero-length special-case rules. It does not rejectClientId length == 0 && CleanSession == 0, and it does not assign a unique ClientId.2. Broker stores the decoded ClientId length, including zero length
File:
wolfMQTT-master/src/mqtt_broker.c:2708For a zero-length ClientId,
id_lenis0. The broker stores that value and continues. There is no MQTT 3.1.1 branch here that changes an empty ClientId into a generated unique ID.3. Empty ClientId skips normal uniqueness/session handling
File:
wolfMQTT-master/src/mqtt_broker.c:2743The uniqueness and existing-session logic is guarded by
BROKER_STR_VALID(bc->client_id). A zero-length ClientId is not a valid non-empty string, so this block is skipped. That means the accepted MQTT 3.1.1 empty-ClientId path is not processed as a generated unique ClientId.4. Default CONNACK state accepts the connection
File:
wolfMQTT-master/src/mqtt_broker.c:2917File:
wolfMQTT-master/src/mqtt_broker.c:3023Unless another branch changes
ack.return_code, the broker sends CONNACK success. The zero-length MQTT 3.1.1 ClientId path does not setMQTT_CONNECT_ACK_CODE_REFUSED_IDand does not assign a unique ClientId before this point.5. Unique assignment exists only in a MQTT v5 conditional branch
File:
wolfMQTT-master/src/mqtt_broker.c:2966File:
wolfMQTT-master/src/mqtt_broker.c:2979File:
wolfMQTT-master/src/mqtt_broker.c:2983This assignment is conditional on:
WOLFMQTT_V5being compiled;bc->client_idbeing empty.It does not apply to MQTT 3.1.1 accepted CONNECT packets.
Dynamic Test Evidence
The existing raw CONNECT test sends zero-length ClientId packets.
Observed MQTT 3.1.1 behavior:
Interpretation:
mqtt311_emptyid_clean1: the broker accepts zero-length ClientId, but the MQTT 3.1.1 path does not assign a visible or internally non-empty unique ClientId before normal processing.mqtt311_emptyid_clean0: the broker accepts a combination that MQTT 3.1.1 requires it to reject with0x02and close.Observed MQTT v5 behavior:
This confirms the issue: unique assignment exists in a MQTT v5 accepted branch, but it is not a uniform MQTT 3.1.1 behavior and is not bound to the required
CleanSession=1condition.Issue Mapping
WOLFMQTT_V5andprotocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5. MQTT 3.1.1 empty ClientId acceptance does not use this path.Inconsistency Reason
MQTT 3.1.1 gives the Server a choice: it may reject a zero-length ClientId, or it may allow it. But once wolfMQTT accepts a zero-length MQTT 3.1.1 ClientId, the standard requires the Server to assign a unique ClientId and process the CONNECT as if that unique ClientId had been supplied.
wolfMQTT does not implement that MQTT 3.1.1 special case uniformly:
CleanSession=1, so it can assign an ID even for invalid empty ClientId +CleanSession=0cases in the MQTT v5 path.Therefore, MQTT 3.1.1 zero-length ClientId handling is incomplete: accepted empty ClientIds are not consistently assigned unique IDs, and invalid
CleanSession=0combinations are not rejected.Conclusion
For MQTT 3.1.1, the unique-assignment behavior should be treated as part of the zero-length ClientId compliance gap:
These issues are closely related to the zero-length ClientId /
CleanSessionvalidation gap, but they describe a different part of the same standard rule: not only mustCleanSession=0be rejected, an accepted zero-length ClientId must also receive a unique ClientId.