AlteriomPainlessMesh v2.1.1
Easy ESP32/ESP8266 mesh networking with Alteriom IoT extensions
Loading...
Searching...
No Matches
buffer.hpp
Go to the documentation of this file.
1#ifndef _PAINLESS_MESH_BUFFER_HPP_
2#define _PAINLESS_MESH_BUFFER_HPP_
3
4#include <list>
5#include <map>
6#include <queue>
7
8#include "Arduino.h"
10
11#ifndef TCP_MSS
12#define TCP_MSS 1024
13#endif
14
15namespace painlessmesh {
16namespace buffer {
17
18// Temporary buffer used by ReceiveBuffer and SentBuffer
20 size_t length = TCP_MSS;
22};
23
27template <class T>
29 public:
30 ReceiveBuffer() { buffer = T(); }
31
35 void push(const char *cstr, size_t length, temp_buffer_t &buf) {
36 auto data_ptr = cstr;
37 do {
38 auto len = strnlen(data_ptr, length);
39 do {
40 // Reserve one byte for the '\0' terminator below: read_len may be at
41 // most buf.length - 1, otherwise buf.buffer[read_len] writes one
42 // byte past the end of the buffer (caught by the ASan CI job).
43 auto read_len = (std::min)(len, buf.length - 1);
44 memcpy(buf.buffer, data_ptr, read_len);
45 buf.buffer[read_len] = '\0';
46 auto newBuffer = T(buf.buffer);
47 stringAppend(buffer, newBuffer);
48 len -= newBuffer.length();
49 length -= newBuffer.length();
50 data_ptr += newBuffer.length() * sizeof(char);
51 } while (len > 0);
52 if (length > 0) {
53 // Skip/remove the '\0' between the messages
54 length -= 1;
55 data_ptr += 1 * sizeof(char);
56 if (buffer.length() > 0) { // skip empty buffers
57 jsonStrings.push_back(buffer);
58 buffer = T();
59 }
60 }
61 } while (length > 0);
62 }
63
67 T front() {
68 if (!empty()) return jsonStrings.front();
69 return T();
70 }
71
75 void pop_front() { jsonStrings.pop_front(); }
76
80 bool empty() { return jsonStrings.empty(); }
81
85 void clear() {
86 jsonStrings.clear();
87 buffer = T();
88 }
89
90 private:
91 T buffer;
92 std::list<T> jsonStrings;
93
98 inline void stringAppend(T &buffer, T &newBuffer) {
99 buffer.concat(newBuffer);
100 };
101};
102
103#ifdef PAINLESSMESH_ENABLE_STD_STRING
104template <>
105inline void ReceiveBuffer<std::string>::stringAppend(std::string &buffer,
106 std::string &newBuffer) {
107 buffer.append(newBuffer);
108}
109#endif
110
111// Maximum number of messages a single connection's SentBuffer may hold
112// (issue #388). Without a cap, a peer that stops draining (stalled TCP
113// connection) grows the outbound list until allocation fails on the ESP8266
114// heap. When the buffer is full, an incoming message evicts the newest
115// message of a strictly lower priority class; if nothing lower-priority is
116// queued, the incoming message itself is rejected (push returns false).
117#ifndef PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES
118#define PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES 64
119#endif
120
124template <class T>
127 uint8_t priority = 2; // 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
128
129 PrioritizedMessage(const T& msg, uint8_t prio = 2) : message(msg), priority(prio) {}
130};
131
136template <class T>
138 public:
140 current_read_iterator = prioritizedMessages.end();
141 };
142
154 bool push(const T &message, bool priority = false) {
155 // Legacy API: map bool to uint8_t priority (false=2 NORMAL, true=1 HIGH)
156 uint8_t priorityLevel = priority ? 1 : 2;
157 return pushWithPriority(message, priorityLevel);
158 }
159
178 bool pushWithPriority(const T &message, uint8_t priorityLevel) {
179 // Clamp priority to valid range
180 if (priorityLevel > 3) priorityLevel = 3;
181
182 if (prioritizedMessages.size() >= PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES &&
183 !evictLowerPriority(priorityLevel)) {
184 messagesDropped++;
185 return false;
186 }
187
188 prioritizedMessages.push_back(PrioritizedMessage<T>(message, priorityLevel));
189
190 // Track statistics
191 totalMessagesQueued++;
192 switch(priorityLevel) {
193 case 0: criticalQueued++; break;
194 case 1: highQueued++; break;
195 case 2: normalQueued++; break;
196 case 3: lowQueued++; break;
197 }
198 return true;
199 }
200
206 size_t requestLength(size_t buffer_length) {
207 // Use highest priority message available
208 auto* msg = getNextMessage();
209 if (!msg) return 0;
210 // A caller with no room is owed nothing. The subtraction below leaves
211 // space for the terminator toCharArray() always writes; on an unsigned
212 // zero it wraps to SIZE_MAX instead, min() then picks the message, and
213 // the answer comes back *larger* than was asked for -- which the
214 // documented contract above forbids and read() then acts on, writing
215 // length + 1 bytes into a buffer the caller said had none.
216 if (buffer_length == 0) return 0;
217 // String.toCharArray automatically turns the last character into
218 // a \0, we need the extra space to deal with that annoyance
219 return (std::min)(buffer_length - 1, msg->length() + 1);
220 }
221
231 void read(size_t length, temp_buffer_t &buf) {
232 // Note that toCharrArray always null terminates
233 // independent of whether the whole string was read so we use one extra
234 // space
235 auto* msg = getNextMessage();
236 if (msg) {
237 msg->toCharArray(buf.buffer, length + 1);
238 last_read_size = length;
239 last_read_priority = getCurrentMessagePriority();
240 }
241 }
242
250 const char *readPtr(size_t length) {
251 auto* msg = getNextMessage();
252 if (msg) {
253 last_read_size = length;
254 last_read_priority = getCurrentMessagePriority();
255 return msg->c_str();
256 }
257 return nullptr;
258 }
259
265 void freeRead() {
266 if (prioritizedMessages.empty()) {
267 last_read_size = 0;
268 current_read_iterator = prioritizedMessages.end();
269 return;
270 }
271
272 // Find and remove the message that was just read (highest priority)
273 auto it = findHighestPriorityMessage();
274 if (it != prioritizedMessages.end()) {
275 auto& msg = it->message;
276
277 if (last_read_size == msg.length() + 1) {
278 // Whole message was read, remove it
279 // Track statistics
280 switch(it->priority) {
281 case 0: criticalSent++; break;
282 case 1: highSent++; break;
283 case 2: normalSent++; break;
284 case 3: lowSent++; break;
285 }
286 prioritizedMessages.erase(it);
287 current_read_iterator = prioritizedMessages.end(); // Reset iterator
288 clean = true;
289 } else {
290 // Partial message read, remove the read portion
291 stringEraseFront(msg, last_read_size);
292 // Keep current_read_iterator pointing to this message for next read
293 clean = false;
294 }
295 }
296 last_read_size = 0;
297 }
298
299 bool empty() { return prioritizedMessages.empty(); }
300
301 void clear() {
302 prioritizedMessages.clear();
303 current_read_iterator = prioritizedMessages.end();
304 messagesDropped = 0;
305 totalMessagesQueued = 0;
306 criticalQueued = highQueued = normalQueued = lowQueued = 0;
307 criticalSent = highSent = normalSent = lowSent = 0;
308 }
309
310 size_t size() { return prioritizedMessages.size(); }
311
315 uint8_t getLastReadPriority() const { return last_read_priority; }
316
320 struct SendStats {
321 uint32_t totalQueued = 0;
322 uint32_t criticalQueued = 0;
323 uint32_t highQueued = 0;
324 uint32_t normalQueued = 0;
325 uint32_t lowQueued = 0;
326 uint32_t criticalSent = 0;
327 uint32_t highSent = 0;
328 uint32_t normalSent = 0;
329 uint32_t lowSent = 0;
330 // Messages lost to the PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES cap:
331 // evicted from the buffer or rejected on push (issue #388)
332 uint32_t dropped = 0;
333 };
334
336 SendStats stats;
337 stats.dropped = messagesDropped;
338 stats.totalQueued = totalMessagesQueued;
339 stats.criticalQueued = criticalQueued;
340 stats.highQueued = highQueued;
341 stats.normalQueued = normalQueued;
342 stats.lowQueued = lowQueued;
343 stats.criticalSent = criticalSent;
344 stats.highSent = highSent;
345 stats.normalSent = normalSent;
346 stats.lowSent = lowSent;
347 return stats;
348 }
349
350 private:
351 size_t last_read_size = 0;
352 uint8_t last_read_priority = 2; // NORMAL default
353 bool clean = true;
354 std::list<PrioritizedMessage<T>> prioritizedMessages;
355 typename std::list<PrioritizedMessage<T>>::iterator current_read_iterator;
356
357 // Statistics tracking
358 uint32_t messagesDropped = 0;
359 uint32_t totalMessagesQueued = 0;
360 uint32_t criticalQueued = 0;
361 uint32_t highQueued = 0;
362 uint32_t normalQueued = 0;
363 uint32_t lowQueued = 0;
364 uint32_t criticalSent = 0;
365 uint32_t highSent = 0;
366 uint32_t normalSent = 0;
367 uint32_t lowSent = 0;
368
377 typename std::list<PrioritizedMessage<T>>::iterator findHighestPriorityMessage() {
378 if (prioritizedMessages.empty()) {
379 return prioritizedMessages.end();
380 }
381
382 // If we're in the middle of a partial read (!clean), continue with current_read_iterator
383 // This maintains backward compatibility with the original buffer behavior
384 if (!clean && current_read_iterator != prioritizedMessages.end()) {
385 return current_read_iterator;
386 }
387
388 // Find message with lowest priority value (0=CRITICAL is highest)
389 auto highest = prioritizedMessages.begin();
390 for (auto it = prioritizedMessages.begin(); it != prioritizedMessages.end(); ++it) {
391 if (it->priority < highest->priority) {
392 highest = it;
393 }
394 }
395
396 // Cache this for partial reads
397 current_read_iterator = highest;
398 return highest;
399 }
400
404 T* getNextMessage() {
405 auto it = findHighestPriorityMessage();
406 if (it != prioritizedMessages.end()) {
407 return &(it->message);
408 }
409 return nullptr;
410 }
411
415 uint8_t getCurrentMessagePriority() {
416 auto it = findHighestPriorityMessage();
417 if (it != prioritizedMessages.end()) {
418 return it->priority;
419 }
420 return 2; // NORMAL default
421 }
422
429 bool evictLowerPriority(uint8_t newPriority) {
430 auto victim = prioritizedMessages.end();
431 for (auto it = prioritizedMessages.begin();
432 it != prioritizedMessages.end(); ++it) {
433 if (!clean && it == current_read_iterator) continue;
434 if (it->priority <= newPriority) continue;
435 // >= keeps walking forward within the lowest class, so the newest
436 // (last-queued) entry of that class is the one dropped
437 if (victim == prioritizedMessages.end() ||
438 it->priority >= victim->priority)
439 victim = it;
440 }
441 if (victim == prioritizedMessages.end()) return false;
442 messagesDropped++;
443 prioritizedMessages.erase(victim);
444 // The cached read iterator may point at the erased node; recompute on
445 // the next read. A partial read in progress (!clean) is never evicted.
446 if (clean) current_read_iterator = prioritizedMessages.end();
447 return true;
448 }
449
450 inline void stringEraseFront(T &string, size_t length) {
451 string.remove(0, length);
452 };
453};
454
455#ifdef PAINLESSMESH_ENABLE_STD_STRING
456template <>
457inline void SentBuffer<std::string>::read(size_t length, temp_buffer_t &buf) {
458 auto* msg = getNextMessage();
459 if (msg) {
460 msg->copy(buf.buffer, length);
461 // Mimic String.toCharArray behaviour, which will insert
462 // null termination at the end of original string and the last
463 // character
464 if (length == msg->length() + 1) buf.buffer[length - 1] = '\0';
465 buf.buffer[length] = '\0';
466 last_read_size = length;
467 last_read_priority = getCurrentMessagePriority();
468 }
469}
470
471template <>
472inline void SentBuffer<std::string>::stringEraseFront(std::string &string,
473 size_t length) {
474 string.erase(0, length);
475};
476#endif
477
478} // namespace buffer
479} // namespace painlessmesh
480#endif
#define TCP_MSS
Definition asynctcp.hpp:9
#define PAINLESSMESH_MAX_SENT_BUFFER_MESSAGES
Definition buffer.hpp:118
ReceivedBuffer handles cstrings and stores them as strings.
Definition buffer.hpp:28
void clear()
Definition buffer.hpp:85
ReceiveBuffer()
Definition buffer.hpp:30
bool empty()
Definition buffer.hpp:80
void push(const char *cstr, size_t length, temp_buffer_t &buf)
Definition buffer.hpp:35
void pop_front()
Definition buffer.hpp:75
T front()
Definition buffer.hpp:67
SentBuffer stores messages (strings) and allows them to be read in any length with priority-based sch...
Definition buffer.hpp:137
SentBuffer()
Definition buffer.hpp:139
bool pushWithPriority(const T &message, uint8_t priorityLevel)
Definition buffer.hpp:178
bool push(const T &message, bool priority=false)
Definition buffer.hpp:154
size_t requestLength(size_t buffer_length)
Definition buffer.hpp:206
void read(size_t length, temp_buffer_t &buf)
Definition buffer.hpp:231
void clear()
Definition buffer.hpp:301
size_t size()
Definition buffer.hpp:310
const char * readPtr(size_t length)
Definition buffer.hpp:250
bool empty()
Definition buffer.hpp:299
void freeRead()
Definition buffer.hpp:265
SendStats getStats() const
Definition buffer.hpp:335
uint8_t getLastReadPriority() const
Definition buffer.hpp:315
Definition wifi.hpp:34
uint8_t priority
Definition buffer.hpp:127
T message
Definition buffer.hpp:126
PrioritizedMessage(const T &msg, uint8_t prio=2)
Definition buffer.hpp:129
uint32_t highSent
Definition buffer.hpp:327
uint32_t normalSent
Definition buffer.hpp:328
uint32_t lowQueued
Definition buffer.hpp:325
uint32_t highQueued
Definition buffer.hpp:323
uint32_t totalQueued
Definition buffer.hpp:321
uint32_t criticalQueued
Definition buffer.hpp:322
uint32_t criticalSent
Definition buffer.hpp:326
uint32_t lowSent
Definition buffer.hpp:329
uint32_t normalQueued
Definition buffer.hpp:324
uint32_t dropped
Definition buffer.hpp:332
Definition buffer.hpp:19
char buffer[TCP_MSS]
Definition buffer.hpp:21
size_t length
Definition buffer.hpp:20