Extend error messages
This commit is contained in:
@@ -1,19 +1,42 @@
|
||||
#include "KOReaderSyncClient.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Logging.h>
|
||||
#include <esp_crt_bundle.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_http_client.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
|
||||
int KOReaderSyncClient::lastHttpCode = 0;
|
||||
int KOReaderSyncClient::lastEspError = 0;
|
||||
unsigned KOReaderSyncClient::lastHeapAtFailure = 0;
|
||||
unsigned KOReaderSyncClient::lastContigHeapAtFailure = 0;
|
||||
const char* KOReaderSyncClient::lastOperation = "";
|
||||
|
||||
namespace {
|
||||
// Static buffer for the detail string returned by lastFailureDetail() — sized to fit
|
||||
// the longest expected message including esp_err name (~32 chars), opcode (~10), heap
|
||||
// numbers, and HTTP status. Single-threaded sync flow makes static safe.
|
||||
char g_failureDetailBuf[160] = {0};
|
||||
|
||||
// Reset the static diagnostic state at the start of each request and capture pre-flight
|
||||
// heap so failure reporting always reflects what was available when the request started.
|
||||
void beginRequest(const char* operation) {
|
||||
KOReaderSyncClient::lastOperation = operation;
|
||||
KOReaderSyncClient::lastEspError = 0;
|
||||
KOReaderSyncClient::lastHttpCode = 0;
|
||||
KOReaderSyncClient::lastHeapAtFailure = ESP.getFreeHeap();
|
||||
KOReaderSyncClient::lastContigHeapAtFailure = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
|
||||
// Device identifier for CrossPoint reader
|
||||
constexpr char DEVICE_NAME[] = "CrossPoint";
|
||||
constexpr char DEVICE_ID[] = "crosspoint-reader";
|
||||
@@ -75,6 +98,24 @@ std::string base64Encode(const std::string& input) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Verify there is enough contiguous heap to attempt a TLS handshake. mbedTLS needs a
|
||||
// large contiguous block during the handshake (~24-32 KB depending on cert chain depth).
|
||||
// Total free heap can mislead because fragmentation leaves no single block big enough,
|
||||
// which is precisely the scenario after recent PNG/JPG decode activity. Returns true if
|
||||
// we should proceed; false means caller must abort with NETWORK_ERROR — in which case
|
||||
// lastFailureDetail() will report the heap shortage instead of attempting a doomed handshake.
|
||||
bool checkHeapForTls() {
|
||||
// beginRequest() already populated lastContigHeapAtFailure for the diagnostic path.
|
||||
if (KOReaderSyncClient::lastContigHeapAtFailure < KOReaderSyncClient::MIN_CONTIG_HEAP_FOR_TLS) {
|
||||
LOG_ERR("KOSync", "Insufficient contiguous heap for TLS: %u available, %u required",
|
||||
KOReaderSyncClient::lastContigHeapAtFailure, KOReaderSyncClient::MIN_CONTIG_HEAP_FOR_TLS);
|
||||
// Synthesize an esp_err_t-shaped value so the diagnostic detail string is uniform.
|
||||
KOReaderSyncClient::lastEspError = ESP_ERR_NO_MEM;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create configured esp_http_client with small TLS buffers
|
||||
esp_http_client_handle_t createClient(const char* url, ResponseBuffer* buf,
|
||||
esp_http_client_method_t method = HTTP_METHOD_GET) {
|
||||
@@ -111,8 +152,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::registerUser() {
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
beginRequest("register");
|
||||
if (!checkHeapForTls()) return NETWORK_ERROR;
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/users/create";
|
||||
LOG_DBG("KOSync", "Registering user: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
||||
LOG_DBG("KOSync", "Registering user: %s (heap: %u, contig: %u)", url.c_str(), lastHeapAtFailure,
|
||||
lastContigHeapAtFailure);
|
||||
|
||||
JsonDocument doc;
|
||||
doc["username"] = KOREADER_STORE.getUsername();
|
||||
@@ -124,7 +169,10 @@ KOReaderSyncClient::Error KOReaderSyncClient::registerUser() {
|
||||
|
||||
ResponseBuffer buf;
|
||||
esp_http_client_handle_t client = createClient(url.c_str(), &buf, HTTP_METHOD_POST);
|
||||
if (!client) return NETWORK_ERROR;
|
||||
if (!client) {
|
||||
lastEspError = ESP_ERR_NO_MEM;
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
esp_http_client_set_post_field(client, body.c_str(), body.length());
|
||||
@@ -132,9 +180,11 @@ KOReaderSyncClient::Error KOReaderSyncClient::registerUser() {
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
const int httpCode = esp_http_client_get_status_code(client);
|
||||
lastHttpCode = httpCode;
|
||||
lastEspError = err;
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
LOG_DBG("KOSync", "Register response: %d (err: %d) | body: %s", httpCode, err, buf.data ? buf.data : "");
|
||||
LOG_DBG("KOSync", "Register response: %d (err: %s) | body: %s", httpCode, esp_err_to_name(err),
|
||||
buf.data ? buf.data : "");
|
||||
|
||||
if (err != ESP_OK) {
|
||||
return NETWORK_ERROR;
|
||||
@@ -168,19 +218,27 @@ KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
beginRequest("auth");
|
||||
if (!checkHeapForTls()) return NETWORK_ERROR;
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth";
|
||||
LOG_DBG("KOSync", "Authenticating: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
||||
LOG_DBG("KOSync", "Authenticating: %s (heap: %u, contig: %u)", url.c_str(), lastHeapAtFailure,
|
||||
lastContigHeapAtFailure);
|
||||
|
||||
ResponseBuffer buf;
|
||||
esp_http_client_handle_t client = createClient(url.c_str(), &buf);
|
||||
if (!client) return NETWORK_ERROR;
|
||||
if (!client) {
|
||||
lastEspError = ESP_ERR_NO_MEM;
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
const int httpCode = esp_http_client_get_status_code(client);
|
||||
lastHttpCode = httpCode;
|
||||
lastEspError = err;
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
LOG_DBG("KOSync", "Auth response: %d (err: %d)", httpCode, err);
|
||||
LOG_DBG("KOSync", "Auth response: %d (err: %s)", httpCode, esp_err_to_name(err));
|
||||
|
||||
if (err != ESP_OK) return NETWORK_ERROR;
|
||||
if (httpCode == 200) return OK;
|
||||
@@ -195,19 +253,27 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
beginRequest("get progress");
|
||||
if (!checkHeapForTls()) return NETWORK_ERROR;
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash;
|
||||
LOG_DBG("KOSync", "Getting progress: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
||||
LOG_DBG("KOSync", "Getting progress: %s (heap: %u, contig: %u)", url.c_str(), lastHeapAtFailure,
|
||||
lastContigHeapAtFailure);
|
||||
|
||||
ResponseBuffer buf;
|
||||
esp_http_client_handle_t client = createClient(url.c_str(), &buf);
|
||||
if (!client) return NETWORK_ERROR;
|
||||
if (!client) {
|
||||
lastEspError = ESP_ERR_NO_MEM;
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
const int httpCode = esp_http_client_get_status_code(client);
|
||||
lastHttpCode = httpCode;
|
||||
lastEspError = err;
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
LOG_DBG("KOSync", "Get progress response: %d (err: %d)", httpCode, err);
|
||||
LOG_DBG("KOSync", "Get progress response: %d (err: %s)", httpCode, esp_err_to_name(err));
|
||||
|
||||
if (err != ESP_OK) return NETWORK_ERROR;
|
||||
|
||||
@@ -242,8 +308,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
beginRequest("update progress");
|
||||
if (!checkHeapForTls()) return NETWORK_ERROR;
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress";
|
||||
LOG_DBG("KOSync", "Updating progress: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
||||
LOG_DBG("KOSync", "Updating progress: %s (heap: %u, contig: %u)", url.c_str(), lastHeapAtFailure,
|
||||
lastContigHeapAtFailure);
|
||||
|
||||
// Build JSON body
|
||||
JsonDocument doc;
|
||||
@@ -260,7 +330,10 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
||||
|
||||
ResponseBuffer buf;
|
||||
esp_http_client_handle_t client = createClient(url.c_str(), &buf, HTTP_METHOD_PUT);
|
||||
if (!client) return NETWORK_ERROR;
|
||||
if (!client) {
|
||||
lastEspError = ESP_ERR_NO_MEM;
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
esp_http_client_set_post_field(client, body.c_str(), body.length());
|
||||
@@ -268,9 +341,10 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
const int httpCode = esp_http_client_get_status_code(client);
|
||||
lastHttpCode = httpCode;
|
||||
lastEspError = err;
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
LOG_DBG("KOSync", "Update progress response: %d (err: %d)", httpCode, err);
|
||||
LOG_DBG("KOSync", "Update progress response: %d (err: %s)", httpCode, esp_err_to_name(err));
|
||||
|
||||
if (err != ESP_OK) return NETWORK_ERROR;
|
||||
if (httpCode == 200 || httpCode == 202) return OK;
|
||||
@@ -278,6 +352,30 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
||||
return SERVER_ERROR;
|
||||
}
|
||||
|
||||
const char* KOReaderSyncClient::lastFailureDetail() {
|
||||
// Heap-pressure case: surfaced when checkHeapForTls() refused before any TCP/TLS work happened.
|
||||
if (lastEspError == ESP_ERR_NO_MEM && lastHttpCode == 0) {
|
||||
snprintf(g_failureDetailBuf, sizeof(g_failureDetailBuf),
|
||||
"%s: low memory (%u free, %u contig, need %u). Reboot device.", lastOperation, lastHeapAtFailure,
|
||||
lastContigHeapAtFailure, MIN_CONTIG_HEAP_FOR_TLS);
|
||||
return g_failureDetailBuf;
|
||||
}
|
||||
// Network/TLS case: esp_http_client_perform() failed before getting a status code.
|
||||
if (lastHttpCode == 0 && lastEspError != 0) {
|
||||
snprintf(g_failureDetailBuf, sizeof(g_failureDetailBuf), "%s: %s (heap %u/%u contig)", lastOperation,
|
||||
esp_err_to_name(lastEspError), lastHeapAtFailure, lastContigHeapAtFailure);
|
||||
return g_failureDetailBuf;
|
||||
}
|
||||
// Server case: got an HTTP status the client didn't recognize as success.
|
||||
if (lastHttpCode != 0) {
|
||||
snprintf(g_failureDetailBuf, sizeof(g_failureDetailBuf), "%s: HTTP %d", lastOperation, lastHttpCode);
|
||||
return g_failureDetailBuf;
|
||||
}
|
||||
// No prior request, or success.
|
||||
g_failureDetailBuf[0] = '\0';
|
||||
return g_failureDetailBuf;
|
||||
}
|
||||
|
||||
const char* KOReaderSyncClient::errorString(Error error) {
|
||||
switch (error) {
|
||||
case OK:
|
||||
|
||||
@@ -71,10 +71,34 @@ class KOReaderSyncClient {
|
||||
static Error updateProgress(const KOReaderProgress& progress);
|
||||
|
||||
/**
|
||||
* Get human-readable error message.
|
||||
* Get human-readable error message (short, for status line).
|
||||
*/
|
||||
static const char* errorString(Error error);
|
||||
|
||||
/**
|
||||
* Get a detailed diagnostic string for the last failure, combining the error
|
||||
* category, the underlying esp_err_t (when applicable), the HTTP status code,
|
||||
* and the free heap at the time of failure. Intended for the SYNC_FAILED screen
|
||||
* so users and bug-reporters can tell network/TLS/server failures apart.
|
||||
* Returns a stable c-string valid until the next request.
|
||||
*/
|
||||
static const char* lastFailureDetail();
|
||||
|
||||
/** HTTP status code from the last request (for diagnostics). */
|
||||
static int lastHttpCode;
|
||||
/** Last esp_err_t from esp_http_client_perform (ESP_OK if request reached the server). */
|
||||
static int lastEspError;
|
||||
/** Free heap (bytes) captured at the start of the last failing request. */
|
||||
static unsigned lastHeapAtFailure;
|
||||
/** Largest contiguous free block (bytes) at the start of the last failing request. */
|
||||
static unsigned lastContigHeapAtFailure;
|
||||
/** Operation tag set at the start of each request, surfaced in lastFailureDetail. */
|
||||
static const char* lastOperation;
|
||||
|
||||
/**
|
||||
* Minimum largest-contiguous-free heap block (bytes) required before attempting
|
||||
* a request. Below this, the client refuses with NETWORK_ERROR and lastFailureDetail
|
||||
* reports a heap-pressure message instead of attempting (and crashing) the TLS handshake.
|
||||
*/
|
||||
static constexpr unsigned MIN_CONTIG_HEAP_FOR_TLS = 32 * 1024;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user