Summary
Two xteink/CrossInk devices reproduce this when browsing a Calibre-Web OPDS catalog:
couldn't allocate memory for buffer
The same server works from Kobo and the Calibre-Web website. The OPDS XML validates with curl/XML tooling, so this looks like a CrossInk OPDS parser heap-pressure issue rather than a malformed server feed.
I coded a small candidate fix and pushed it here:
I attempted to open this as a PR, but GitHub returned:
gershalfred does not have the correct permissions to execute CreatePullRequest
So I’m posting the full diagnosis here for maintainers to pull/cherry-pick or recreate.
User-visible symptom
While browsing an OPDS catalog on xteink/CrossInk:
- root/server access succeeds
- browsing some feeds may work briefly
- selecting Authors / Books / an author can fail with:
couldn't allocate memory for buffer
This was verified on two xteink devices.
Why this appears firmware-side
The exact message maps to the OPDS parser path in:
lib/OpdsParser/OpdsParser.cpp
The failure path is:
void* const buf = XML_GetBuffer(parser, chunkSize);
if (!buf) {
errorOccured = true;
errorReason = OpdsParserError::BUFFER_MEMORY;
LOG_DBG("OPDS", "Couldn't allocate memory for buffer");
destroyXmlParser(parser);
parser = nullptr;
return length;
}
So the message means Expat could not allocate its parse buffer on-device.
Why Calibre-Web is probably not the root cause
The same Calibre-Web server:
- works from Kobo
- works from the Calibre-Web website
- returns valid OPDS XML via curl/XML validation
- returns normal sized feeds from server-side tests
A temporary server-side compatibility shim with tiny feeds reduces the issue, which further points to device-side memory pressure during OPDS parsing.
Suspected regression class
This may be related to the broader OPDS/networking changes where HttpDownloader moved to esp_http_client and buffering was tuned for download reliability/speed.
Those changes are useful for actual EPUB downloads, but catalog browsing has a different memory shape:
- Wi-Fi/TLS /
esp_http_client buffers are live
- the fetch body buffer is live
- Expat asks for a parse buffer
- parsed OPDS entries and their
std::string fields are retained
On ESP32-C3/no-PSRAM hardware, this can leave insufficient contiguous heap for XML_GetBuffer(...) even when the XML is valid.
Candidate fix coded
The candidate fix separates catalog fetches from file downloads:
- Keep book/file downloads at the current larger buffer:
DEFAULT_DOWNLOAD_BUFFER_SIZE = 2048
- Add a smaller default for catalog/text fetches:
DEFAULT_FETCH_BUFFER_SIZE = 512
-
Make HttpDownloader::fetchUrl(...) use the smaller fetch buffer.
-
Reduce Expat parse chunk size:
That lowers transient contiguous heap requirements while browsing catalogs, without intentionally slowing actual EPUB downloads.
Patch
diff --git a/lib/OpdsParser/OpdsParser.cpp b/lib/OpdsParser/OpdsParser.cpp
index 2a935947..e47c0200 100644
--- a/lib/OpdsParser/OpdsParser.cpp
+++ b/lib/OpdsParser/OpdsParser.cpp
@@ -34,7 +34,9 @@ size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) {
const char* currentPos = reinterpret_cast<const char*>(xmlData);
size_t remaining = length;
- constexpr size_t chunkSize = 1024;
+ // Keep Expat's transient parse buffer small; OPDS parsing happens while the
+ // HTTP/TLS stack and entry strings are also consuming fragmented heap.
+ constexpr size_t chunkSize = 256;
while (remaining > 0) {
const size_t toRead = remaining < chunkSize ? remaining : chunkSize;
diff --git a/src/network/HttpDownloader.cpp b/src/network/HttpDownloader.cpp
index ed9ccdf7..a9e9c389 100644
--- a/src/network/HttpDownloader.cpp
+++ b/src/network/HttpDownloader.cpp
@@ -25,6 +25,9 @@ constexpr int HTTP_TX_BUF = 1024;
constexpr int HTTP_TIMEOUT_MS = 60000;
constexpr int HTTP_READ_POLL_TIMEOUT_MS = 5000;
constexpr uint32_t DOWNLOAD_IDLE_TIMEOUT_MS = 30000;
+// Catalog fetches stream into parsers while WiFi/TLS buffers are live. Keep
+// their body buffer smaller than file downloads to preserve contiguous heap.
+constexpr size_t DEFAULT_FETCH_BUFFER_SIZE = 512;
constexpr size_t DEFAULT_DOWNLOAD_BUFFER_SIZE = 2048;
constexpr uint8_t MAX_REDIRECTS = 5;
@@ -415,7 +418,7 @@ bool HttpDownloader::fetchUrl(const std::string& url, const DataCallback& onData
Sink sink;
sink.write = onData;
- return runGet(url, username, password, sink, DEFAULT_DOWNLOAD_BUFFER_SIZE) == OK;
+ return runGet(url, username, password, sink, DEFAULT_FETCH_BUFFER_SIZE) == OK;
}
Local verification
This passed:
I attempted:
but my local checkout fails before compiling because the SDK/library symlink target is missing:
PackageException: Can not create a symbolic link for `freeink-sdk/libs/hardware/BatteryMonitor`, not a directory
So this needs CI or a maintainer machine with the SDK checkout available for compile validation.
Hardware verification request
On a device that reproduces the issue:
- Configure a Calibre-Web OPDS server.
- Browse root → Authors → letter → author.
- Browse root → Books.
- Confirm the device no longer shows:
couldn't allocate memory for buffer
- Confirm selecting/downloading an EPUB still works and download speed is not materially regressed.
Why this should be safe
This does not remove the faster file download path. It only makes catalog/text fetches and XML parsing less demanding on contiguous heap while browsing.
Summary
Two xteink/CrossInk devices reproduce this when browsing a Calibre-Web OPDS catalog:
The same server works from Kobo and the Calibre-Web website. The OPDS XML validates with curl/XML tooling, so this looks like a CrossInk OPDS parser heap-pressure issue rather than a malformed server feed.
I coded a small candidate fix and pushed it here:
I attempted to open this as a PR, but GitHub returned:
So I’m posting the full diagnosis here for maintainers to pull/cherry-pick or recreate.
User-visible symptom
While browsing an OPDS catalog on xteink/CrossInk:
This was verified on two xteink devices.
Why this appears firmware-side
The exact message maps to the OPDS parser path in:
The failure path is:
So the message means Expat could not allocate its parse buffer on-device.
Why Calibre-Web is probably not the root cause
The same Calibre-Web server:
A temporary server-side compatibility shim with tiny feeds reduces the issue, which further points to device-side memory pressure during OPDS parsing.
Suspected regression class
This may be related to the broader OPDS/networking changes where
HttpDownloadermoved toesp_http_clientand buffering was tuned for download reliability/speed.Those changes are useful for actual EPUB downloads, but catalog browsing has a different memory shape:
esp_http_clientbuffers are livestd::stringfields are retainedOn ESP32-C3/no-PSRAM hardware, this can leave insufficient contiguous heap for
XML_GetBuffer(...)even when the XML is valid.Candidate fix coded
The candidate fix separates catalog fetches from file downloads:
Make
HttpDownloader::fetchUrl(...)use the smaller fetch buffer.Reduce Expat parse chunk size:
That lowers transient contiguous heap requirements while browsing catalogs, without intentionally slowing actual EPUB downloads.
Patch
Local verification
This passed:
I attempted:
but my local checkout fails before compiling because the SDK/library symlink target is missing:
So this needs CI or a maintainer machine with the SDK checkout available for compile validation.
Hardware verification request
On a device that reproduces the issue:
Why this should be safe
This does not remove the faster file download path. It only makes catalog/text fetches and XML parsing less demanding on contiguous heap while browsing.