Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e569a74
add defines for needed PROGMEM functions
Jul 11, 2025
1a6d44b
implemented writeString(const __FlashStringHelper* ...), writeString_P()
Jul 11, 2025
d2fa270
Merge branch 'master' into implement-fstring-topics
Jul 11, 2025
07efb35
Merge branch 'master' into implement-fstring-topics
hmueller01 Jul 11, 2025
64709f1
fix merge failure
hmueller01 Jul 13, 2025
b2fb86c
added writeString(const __FlashStringHelper* ...), writeString_P()
hmueller01 Jul 13, 2025
c461807
added __FlashStringHelper
hmueller01 Jul 13, 2025
ff1997e
Merge branch 'master' into implement-fstring-topics
hmueller01 Jul 13, 2025
5019584
Merge branch 'master' into implement-fstring-topics
hmueller01 Jul 13, 2025
79bd37d
Merge branch 'master' into implement-fstring-topics
hmueller01 Jul 19, 2025
f8374e8
Merge branch 'master' into implement-fstring-topics
hmueller01 Oct 20, 2025
07ebf85
do not use prog_uint8_t, deprecated
hmueller01 Oct 20, 2025
42d278f
Merge branch 'master' into implement-fstring-topics
hmueller01 Oct 21, 2025
92ce07f
Merge branch 'master' into implement-fstring-topics
hmueller01 Oct 23, 2025
3de4409
removed writeString(__FlashStringHelper*), added beginPublishImpl(), …
hmueller01 Oct 23, 2025
2df1cf2
added F() macro
hmueller01 Oct 23, 2025
30d9095
Merge branch 'master' into implement-fstring-topics
hmueller01 Oct 23, 2025
1aec205
implemented writeStringImpl() using template, added __FlashStringHelp…
hmueller01 Oct 24, 2025
3ba5637
fixed wrong return type of writeStringImpl()
hmueller01 Oct 24, 2025
1fdd6dc
shorten source code size for getting string length
hmueller01 Oct 24, 2025
541876a
removed template implementation by classic parameter
hmueller01 Oct 29, 2025
ee3b134
Merge branch 'master' into implement-fstring-topics
hmueller01 Oct 29, 2025
2d7b1b2
added publish(__FlashStringHelper* topic, __FlashStringHelper* payloa…
hmueller01 Oct 29, 2025
2fe356c
make all only beginPublishImpl / writeStringImpl calling functions in…
hmueller01 Oct 30, 2025
02f55f9
fix compiler error: moved public inline functions to header file
hmueller01 Nov 2, 2025
ae7cd3e
fix compiler error: moved public inline functions to header file
hmueller01 Nov 2, 2025
5ea9b74
implement write_P(PGM_P string)
hmueller01 Nov 2, 2025
443097d
Initial revision
hmueller01 Nov 2, 2025
1a3b765
fix arduino-cli compile error
hmueller01 Nov 2, 2025
b7fe4c3
implemented bool subscribeImpl(bool progmem, ...) and bool unsubscrib…
hmueller01 Nov 5, 2025
f455953
added subscribe examples
hmueller01 Nov 6, 2025
4168491
Merge branch 'master' into implement-fstring-topics
hmueller01 Nov 6, 2025
54de124
Merge branch 'master' into implement-fstring-topics
hmueller01 Nov 9, 2025
f79d377
Merge branch 'master' into implement-fstring-topics
hmueller01 Nov 9, 2025
1676861
tag unused parameter
hmueller01 Nov 9, 2025
d1d75ed
Merge branch 'master' into implement-fstring-topics
hmueller01 Nov 10, 2025
94a83df
use C++ casting
hmueller01 Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 93 additions & 15 deletions src/PubSubClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,15 @@ bool PubSubClient::publish(const char* topic, const char* payload, bool retained
return publish(topic, payload, MQTT_QOS0, retained);
}

bool PubSubClient::publish(const __FlashStringHelper* topic, const char* payload, uint8_t qos, bool retained) {
return publish(topic, (const uint8_t*)payload, payload ? strnlen(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained);
}

bool PubSubClient::publish(const __FlashStringHelper* topic, const __FlashStringHelper* payload, uint8_t qos, bool retained) {
return publish_P(topic, (const uint8_t*)payload, payload ? strnlen_P(reinterpret_cast<const char*>(payload), MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a number of strnlen (and _P) checks, which are all against either max. Packet size or buffer size.
However in a packet/buffer, there is also the topic and some header, so those checks are still allowing for some overflow.
The max. possible packet size is probably not really a problem as we won't get that large payloads, or at least not from a char pointer.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand the max. length is only there to limit strlen somewhere if the payload is corrupted. Or who wants to send ~268MB payload on a micro controller?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we really need strnlen_P() or if we just could use strlen_P() and let the build_header() do the check work ...

retained);
}

bool PubSubClient::publish(const char* topic, const char* payload, uint8_t qos, bool retained) {
return publish(topic, (const uint8_t*)payload, payload ? strnlen(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained);
}
Expand All @@ -545,11 +554,23 @@ bool PubSubClient::publish(const char* topic, const uint8_t* payload, size_t ple
return false;
}

bool PubSubClient::publish_P(const char* topic, const char* payload, bool retained) {
bool PubSubClient::publish(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) {
if (beginPublish(topic, plength, qos, retained)) {
size_t rc = write(payload, plength);
return endPublish() && (rc == plength);
}
return false;
}

bool PubSubClient::publish_P(const char* topic, PGM_P payload, bool retained) {
return publish_P(topic, payload, MQTT_QOS0, retained);
}

bool PubSubClient::publish_P(const char* topic, const char* payload, uint8_t qos, bool retained) {
bool PubSubClient::publish_P(const __FlashStringHelper* topic, PGM_P payload, uint8_t qos, bool retained) {
return publish_P(topic, (const uint8_t*)payload, payload ? strnlen_P(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained);
}

bool PubSubClient::publish_P(const char* topic, PGM_P payload, uint8_t qos, bool retained) {
return publish_P(topic, (const uint8_t*)payload, payload ? strnlen_P(payload, MQTT_MAX_POSSIBLE_PACKET_SIZE) : 0, qos, retained);
}

Expand All @@ -565,27 +586,51 @@ bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, size_t p
return false;
}

bool PubSubClient::publish_P(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained) {
if (beginPublish(topic, plength, qos, retained)) {
size_t rc = write_P(payload, plength);
return endPublish() && (rc == plength);
}
return false;
}

bool PubSubClient::beginPublish(const char* topic, size_t plength, bool retained) {
return beginPublish(topic, plength, MQTT_QOS0, retained);
}

bool PubSubClient::beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained) {
/**
* @brief Internal beginPublish implementation using topic stored in RAM or PROGMEM.
*
* @param progmem true if the topic is stored in PROGMEM/Flash, false if in RAM.
* @param topic The topic to publish to.
* @param plength The length of the payload.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool PubSubClient::beginPublishImpl(bool progmem, const char* topic, size_t plength, uint8_t qos, bool retained) {
if (!topic) return false;
if (strlen(topic) == 0) return false; // empty topic is not allowed
if (qos > MQTT_QOS2) { // only valid QoS supported

// get topic length depending on storage (RAM vs PROGMEM)
size_t topicLen = progmem ? strlen_P(topic) : strlen(topic);
if (topicLen == 0) return false; // empty topic is not allowed

if (qos > MQTT_QOS2) { // only valid QoS supported
ERROR_PSC_PRINTF_P("beginPublish() called with invalid QoS %u\n", qos);
return false;
}

const size_t nextMsgLen = (qos > MQTT_QOS0) ? 2 : 0; // add 2 bytes for nextMsgId if QoS > 0
// check if the header, the topic (including 2 length bytes) and nextMsgId fit into the _buffer
if (connected() && (MQTT_MAX_HEADER_SIZE + strlen(topic) + 2 + nextMsgLen <= _bufferSize)) {
if (connected() && (MQTT_MAX_HEADER_SIZE + topicLen + 2 + nextMsgLen <= _bufferSize)) {
// first write the topic at the end of the maximal variable header (MQTT_MAX_HEADER_SIZE) to the _buffer
size_t topicLen = writeString(topic, MQTT_MAX_HEADER_SIZE) - MQTT_MAX_HEADER_SIZE;
topicLen = writeStringImpl(progmem, topic, MQTT_MAX_HEADER_SIZE) - MQTT_MAX_HEADER_SIZE;
if (qos > MQTT_QOS0) {
// if QoS 1 or 2, we need to send the nextMsgId (packet identifier) after topic
writeNextMsgId(MQTT_MAX_HEADER_SIZE + topicLen);
}
// we now know the length of the topic string (lenght + 2 bytes signalling the length) and can build the variable header information
// we now know the length of the topic string (length + 2 bytes signalling the length) and can build the variable header information
const uint8_t header = MQTTPUBLISH | MQTT_QOS_GET_HDR(qos) | (retained ? MQTTRETAINED : 0);
uint8_t hdrLen = buildHeader(header, topicLen + nextMsgLen + plength);
if (hdrLen == 0) return false; // exit here in case of header generation failure
Expand All @@ -597,6 +642,19 @@ bool PubSubClient::beginPublish(const char* topic, size_t plength, uint8_t qos,
return false;
}

bool PubSubClient::beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained) {
return beginPublishImpl(false, topic, plength, qos, retained);
}

bool PubSubClient::beginPublish(const __FlashStringHelper* topic, size_t plength, uint8_t qos, bool retained) {
// convert FlashStringHelper in PROGMEM-pointer
return beginPublishImpl(true, reinterpret_cast<const char*>(topic), plength, qos, retained);
}

bool PubSubClient::beginPublish_P(PGM_P topic, size_t plength, uint8_t qos, bool retained) {
return beginPublishImpl(true, reinterpret_cast<const char*>(topic), plength, qos, retained);
}

bool PubSubClient::endPublish() {
if (connected()) {
if (_bufferWritePos > 0) {
Expand Down Expand Up @@ -632,7 +690,7 @@ uint8_t PubSubClient::buildHeader(uint8_t header, size_t length) {
} while ((len > 0) && (hdrLen < MQTT_MAX_HEADER_SIZE - 1));

if (len > 0) {
ERROR_PSC_PRINTF_P("buildHeader() length too big %zu, left %zu\n", length, len);
ERROR_PSC_PRINTF_P("buildHeader: header=0x%02X, length too big %zu, left %zu\n", header, length, len);
return 0;
}

Expand Down Expand Up @@ -665,7 +723,7 @@ size_t PubSubClient::write_P(const uint8_t* buf, size_t size) {
*
* @param header Header byte, e.g. MQTTCONNECT, MQTTPUBLISH, MQTTSUBSCRIBE, MQTTUNSUBSCRIBE.
* @param length Length of _buffer to write.
* @return True if successfully sent, otherwise false if buildHeader() failed or buffer could not be written.
* @return True if successfully sent, otherwise false if build header failed or buffer could not be written.
*/
bool PubSubClient::writeControlPacket(uint8_t header, size_t length) {
uint8_t hdrLen = buildHeader(header, length);
Expand Down Expand Up @@ -713,30 +771,50 @@ size_t PubSubClient::writeBuffer(size_t pos, size_t size) {
}

/**
* @brief Write an UTF-8 encoded string to the internal buffer at a given position. The string can have a length of 0 to 65535 bytes (depending on size of
* @brief Internal implementation of writeString using RAM or PROGMEM string.
* Write an UTF-8 encoded string to the internal buffer at a given position. The string can have a length of 0 to 65535 bytes (depending on size of
* internal buffer). The buffer is prefixed with two bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification.
* @note If the string does not fit in the buffer or is longer than 65535 bytes nothing is written to the buffer and the returned position is
* unchanged.
*
* @param progmem true if the string is stored in PROGMEM, false if in RAM.
* @param string 'C' string of the data that shall be written in the buffer.
* @param pos Position in the internal buffer to write the string.
* @return New position in the internal buffer (pos + 2 + string length), or pos if a buffer overrun would occur or the string is a nullptr.
*/
size_t PubSubClient::writeString(const char* string, size_t pos) {
size_t PubSubClient::writeStringImpl(bool progmem, const char* string, size_t pos) {
if (!string) return pos;

size_t sLen = strlen(string);
size_t sLen = progmem ? strlen_P(string) : strlen(string);
if ((pos + 2 + sLen <= _bufferSize) && (sLen <= 0xFFFF)) {
_buffer[pos++] = (uint8_t)(sLen >> 8);
_buffer[pos++] = (uint8_t)(sLen & 0xFF);
memcpy(_buffer + pos, string, sLen);
if (progmem) {
memcpy_P(_buffer + pos, string, sLen);
} else {
memcpy(_buffer + pos, string, sLen);
}
pos += sLen;
} else {
ERROR_PSC_PRINTF_P("writeString(): string (%zu) does not fit into buf (%zu)\n", pos + 2 + sLen, _bufferSize);
ERROR_PSC_PRINTF_P("writeStringImpl(): string (%zu) does not fit into buf (%zu)\n", pos + 2 + sLen, _bufferSize);
}
return pos;
}

/**
* @brief Write an UTF-8 encoded string to the internal buffer at a given position. The string can have a length of 0 to 65535 bytes (depending on size of
* internal buffer). The buffer is prefixed with two bytes representing the length of the string. See section 1.5.3 of MQTT v3.1.1 protocol specification.
* @note If the string does not fit in the buffer or is longer than 65535 bytes nothing is written to the buffer and the returned position is
* unchanged.
*
* @param string 'C' string of the data that shall be written in the buffer.
* @param pos Position in the internal buffer to write the string.
* @return New position in the internal buffer (pos + 2 + string length), or pos if a buffer overrun would occur or the string is a nullptr.
*/
size_t PubSubClient::writeString(const char* string, size_t pos) {
return writeStringImpl(false, string, pos);
}

/**
* @brief Write nextMsgId to the internal buffer at the given position.
* @note If the nextMsgId (2 bytes) does not fit in the buffer nothing is written to the buffer and the returned position is unchanged.
Expand Down
98 changes: 96 additions & 2 deletions src/PubSubClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,12 @@ class PubSubClient : public Print {
uint8_t buildHeader(uint8_t header, size_t length);
bool writeControlPacket(uint8_t header, size_t length);
size_t writeBuffer(size_t pos, size_t size);
size_t writeStringImpl(bool progmem, const char* string, size_t pos);
size_t writeString(const char* string, size_t pos);
size_t writeNextMsgId(size_t pos);

bool beginPublishImpl(bool progmem, const char* topic, size_t plength, uint8_t qos, bool retained);

// Add to buffer and flush if full (only to be used with beginPublish/endPublish)
size_t appendBuffer(uint8_t data);
size_t flushBuffer();
Expand Down Expand Up @@ -524,6 +527,28 @@ class PubSubClient : public Print {
*/
bool publish(const char* topic, const char* payload, uint8_t qos, bool retained);

/**
* @brief Publishes a message to the specified topic.
* @param topic The topic from __FlashStringHelper to publish to.
* @param payload The message to publish.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish(const __FlashStringHelper* topic, const char* payload, uint8_t qos, bool retained);

/**
* @brief Publishes a message from __FlashStringHelper to the specified topic from __FlashStringHelper.
* @param topic The topic to publish to.
* @param payload The message to publish.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish(const __FlashStringHelper* topic, const __FlashStringHelper* payload, uint8_t qos, bool retained);

/**
* @brief Publishes a non retained message to the specified topic using QoS 0.
* @param topic The topic to publish to.
Expand Down Expand Up @@ -557,6 +582,18 @@ class PubSubClient : public Print {
*/
bool publish(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained);

/**
* @brief Publishes a message to the specified topic.
* @param topic The topic from __FlashStringHelper to publish to.
* @param payload The message to publish.
* @param plength The length of the payload.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained);

/**
* @brief Publishes a message stored in PROGMEM to the specified topic using QoS 0.
* @param topic The topic to publish to.
Expand All @@ -565,7 +602,7 @@ class PubSubClient : public Print {
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish_P(const char* topic, const char* payload, bool retained);
bool publish_P(const char* topic, PGM_P payload, bool retained);

/**
* @brief Publishes a message stored in PROGMEM to the specified topic.
Expand All @@ -576,7 +613,18 @@ class PubSubClient : public Print {
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish_P(const char* topic, const char* payload, uint8_t qos, bool retained);
bool publish_P(const char* topic, PGM_P payload, uint8_t qos, bool retained);

/**
* @brief Publishes a message stored in PROGMEM to the specified topic.
* @param topic The topic from __FlashStringHelper to publish to.
* @param payload The message to publish.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish_P(const __FlashStringHelper* topic, PGM_P payload, uint8_t qos, bool retained);

/**
* @brief Publishes a message stored in PROGMEM to the specified topic using QoS 0.
Expand All @@ -601,6 +649,18 @@ class PubSubClient : public Print {
*/
bool publish_P(const char* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained);

/**
* @brief Publishes a message stored in PROGMEM to the specified topic.
* @param topic The topic from __FlashStringHelper to publish to.
* @param payload The message from PROGMEM to publish.
* @param plength The length of the payload.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool publish_P(const __FlashStringHelper* topic, const uint8_t* payload, size_t plength, uint8_t qos, bool retained);

/**
* @brief Start to publish a message using QoS 0.
* This API:
Expand Down Expand Up @@ -634,6 +694,40 @@ class PubSubClient : public Print {
*/
bool beginPublish(const char* topic, size_t plength, uint8_t qos, bool retained);

/**
* @brief Start to publish a message using a topic from __FlashStringHelper F().
* This API:
* beginPublish(...)
* one or more calls to write(...)
* endPublish()
* Allows for arbitrarily large payloads to be sent without them having to be copied into
* a new buffer and held in memory at one time.
* @param topic The topic from __FlashStringHelper to publish to.
* @param plength The length of the payload.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool beginPublish(const __FlashStringHelper* topic, size_t plength, uint8_t qos, bool retained);

/**
* @brief Start to publish a message using a topic in PROGMEM.
* This API:
* beginPublish_P(...)
* one or more calls to write(...)
* endPublish()
* Allows for arbitrarily large payloads to be sent without them having to be copied into
* a new buffer and held in memory at one time.
* @param topic The topic in PROGMEM to publish to.
* @param plength The length of the payload.
* @param qos The quality of service (\ref group_qos) to publish at. [0, 1, 2].
* @param retained Publish the message with the retain flag.
* @return true If the publish succeeded.
* false If the publish failed, either connection lost or message too large.
*/
bool beginPublish_P(PGM_P topic, size_t plength, uint8_t qos, bool retained);

/**
* @brief Finish sending a message that was started with a call to beginPublish.
* @return true If the publish succeeded.
Expand Down
5 changes: 5 additions & 0 deletions tests/src/lib/Arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ extern void loop(void);
unsigned long millis(void);
}

class __FlashStringHelper;
#define PROGMEM
#define PGM_P const char*
#define memcpy_P memcpy
#define strlen_P strlen
#define strnlen_P strnlen
#define pgm_read_byte_near(x) *(x)
#define F(x) (reinterpret_cast<const __FlashStringHelper*>(x))

#define yield(x) {}

Expand Down