diff --git a/lib/obp60task/NetworkClient.cpp b/lib/obp60task/NetworkClient.cpp index 4d091f5..53ac1c6 100644 --- a/lib/obp60task/NetworkClient.cpp +++ b/lib/obp60task/NetworkClient.cpp @@ -104,8 +104,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou free(buffer); return false; } - else{ - + else{ if (DEBUGING) { String ce = http.header("Content-Encoding"); String te = http.header("Transfer-Encoding"); @@ -132,9 +131,18 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou String ce = http.header("Content-Encoding"); bool isGzip = ce.equalsIgnoreCase("gzip"); + // NEW: read expected body size if provided by server (prevents waiting forever for missing bytes) + int total = http.getSize(); // returns Content-Length, or -1 if unknown/chunked + + // NEW: fail fast if server claims something larger than our buffer + if (total > 0 && (size_t)total > capacity) { + Serial.println("Response exceeds READLIMIT."); + aborting = true; + } + // NEW: if not gzip, we will not try to decompress (prevents false "Decompress OK" / random success) // You can either handle plain JSON here or just fail-fast. - if (!isGzip) { + if (!isGzip && !aborting) { if (DEBUGING) { Serial.println("Server response is NOT gzip (Content-Encoding != gzip)."); Serial.println("Either disable Accept-Encoding: gzip or add plain-body handling here."); @@ -142,7 +150,6 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou // --- Plain-body handling (recommended): read full body into outData as-is --- // NEW: try to read Content-Length bytes if available (more robust) - int total = http.getSize(); // returns Content-Length, or -1 if unknown/chunked if (total > 0 && (size_t)total > capacity) { Serial.println("Plain response exceeds READLIMIT."); aborting = true; @@ -175,6 +182,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou lastData = millis(); } + // NEW: stop reading as soon as we have the full response if (total > 0 && (int)len >= total) { break; // we got full body } @@ -208,69 +216,94 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou } // --- GZIP path (only if Content-Encoding is gzip) --- - while ((http.connected() || (stream && stream->available())) && !complete) { + if (!aborting) { - size_t avail = stream ? stream->available() : 0; + // NEW: read exactly Content-Length bytes when available (prevents partial-body timeout loops) + while ((http.connected() || (stream && stream->available())) && !complete && !aborting) { - if (avail == 0) { - if (millis() - lastData > READ_TIMEOUT) { - Serial.println("TIMEOUT waiting for data!"); - aborting = true; // NEW: mark abnormal exit + size_t avail = stream ? stream->available() : 0; + + if (avail == 0) { + // NEW: if Content-Length is known and we already read it all, stop immediately + if (total > 0 && (int)len >= total) { + break; + } + + if (millis() - lastData > READ_TIMEOUT) { + if (DEBUGING) {Serial.println("TIMEOUT waiting for data!");} + aborting = true; // NEW: mark abnormal exit + break; + } + delay(1); + continue; + } + + // NEW: safety check if buffer limit is reached + if (len >= capacity) { + Serial.println("READLIMIT reached, aborting."); + aborting = true; + break; + } + + // NEW: if Content-Length is known, do not read beyond it + if (total > 0) { + size_t remaining = (size_t)total - len; + if (avail > remaining) avail = remaining; + } + + if (len + avail > capacity) + avail = capacity - len; + + int read = stream->readBytes(buffer + len, avail); + if (read <= 0) { + // NEW: avoid tight loop if read returns zero + delay(1); + continue; + } + + len += (size_t)read; + lastData = millis(); + + if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);} + + // NEW: if Content-Length is known and fully received, we can stop reading + if (total > 0 && (int)len >= total) { break; } - delay(1); - continue; } - // NEW: safety check if buffer limit is reached - if (len >= capacity) { - Serial.println("READLIMIT reached, aborting."); - aborting = true; - break; + // NEW: only attempt gzip parse/decompress after we have a complete body (when Content-Length is known) + // This avoids wasting heap with repeated malloc/free and reduces fragmentation over long runtimes. + if (!aborting) { + if (len < 20) { + aborting = true; + } else { + int headerOffset = skipGzipHeader(buffer, len); + if (headerOffset < 0) { + aborting = true; + } else { + unsigned long testLen = len * 8; // Dynamic expansion + uint8_t* test = (uint8_t*)malloc(testLen); + + if (!test) { + Serial.println("Malloc failed test buffer, aborting."); + aborting = true; + } else { + unsigned long srcLen = (unsigned long)(len - (size_t)headerOffset); + int res = puff(test, &testLen, buffer + headerOffset, &srcLen); + + if (res == 0) { + if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);} + outData = test; + outLen = (size_t)testLen; + complete = true; + } else { + free(test); + } + } + } + } } - - if (len + avail > capacity) - avail = capacity - len; - - int read = stream->readBytes(buffer + len, avail); - if (read <= 0) { - // NEW: avoid tight loop if read returns zero - delay(1); - continue; - } - - len += (size_t)read; - lastData = millis(); - - if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);} - - if (len < 20) continue; // Not enough data for header - - int headerOffset = skipGzipHeader(buffer, len); - if (headerOffset < 0) continue; - - unsigned long testLen = len * 8; // Dynamic expansion - uint8_t* test = (uint8_t*)malloc(testLen); - - if (!test) { - // NEW: abort if allocation fails to prevent endless retry loop - Serial.println("Malloc failed test buffer, aborting."); - aborting = true; - break; - } - - unsigned long srcLen = len - headerOffset; - - int res = puff(test, &testLen, buffer + headerOffset, &srcLen); - if (res == 0) { - if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);} - outData = test; - outLen = (size_t)testLen; - complete = true; - break; - } - - free(test); } // --- Added: Force-close connection only if aborted to avoid TCP RST storms ---