mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Replace single retry counter with separate stalled-attempt and total-attempt limits. This allows downloads to recover from early truncations (common in first ~100KB during heap settling) while still protecting against dead servers. Also removes HAVE_MAX_FRAGMENT flag due to memory corruption issues with ADEPT servers and removes unused decrypt method from ContentProtection.
147 lines
5.4 KiB
C++
147 lines
5.4 KiB
C++
// SD/HAL binding for the content-protection read path.
|
|
//
|
|
// The ContentProtection SDK lib is storage-agnostic (it works against a
|
|
// ByteSource). This file is the firmware-side glue that backs that seam with
|
|
// the device's SD storage: a HalStorage-backed ByteSource, the credential
|
|
// lookup, and the openProtectedBook() entry point the reader calls. It lives in
|
|
// the firmware — not the SDK lib — so the portable lib carries no HAL dependency.
|
|
|
|
#include <ByteSource.h>
|
|
#include <ContentProtection.h>
|
|
#include <Credential.h>
|
|
#include <HalStorage.h>
|
|
#include <Memory.h>
|
|
#include <ProtectedBook.h>
|
|
#include <WolfsslCrypto.h>
|
|
#include <Zip.h>
|
|
#include <esp_heap_caps.h>
|
|
#include <time.h>
|
|
|
|
namespace freeink {
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
// The access credential is provisioned off-device and dropped here.
|
|
// Generic path — the reader carries no scheme name.
|
|
constexpr const char* kCredentialPath = "/.crosspoint/content.key";
|
|
|
|
// One shared crypto backend for the whole read path.
|
|
WolfsslCrypto& crypto() {
|
|
static WolfsslCrypto instance;
|
|
return instance;
|
|
}
|
|
|
|
// ByteSource over an SD file (read-only). One open handle per instance.
|
|
class SdByteSource : public ByteSource {
|
|
public:
|
|
explicit SdByteSource(std::string path) : path_(std::move(path)) {}
|
|
bool open() {
|
|
file_ = Storage.open(path_.c_str(), O_RDONLY);
|
|
return file_ && file_.isOpen();
|
|
}
|
|
int32_t readAt(uint64_t offset, void* dst, uint32_t len) override {
|
|
if (!file_ || !file_.seek64(offset)) return -1;
|
|
return file_.read(dst, len);
|
|
}
|
|
uint64_t size() const override { return file_ ? file_.fileSize64() : 0; }
|
|
|
|
private:
|
|
std::string path_;
|
|
mutable HalFile file_;
|
|
};
|
|
|
|
// Adapts an opened ProtectedBook to the reader-facing access interface.
|
|
class ProtectedBookDecryptor : public ContentDecryptor {
|
|
public:
|
|
ProtectedBookDecryptor(std::string epubPath, std::unique_ptr<ProtectedBook> book)
|
|
: epubPath_(std::move(epubPath)), book_(std::move(book)) {}
|
|
|
|
bool isEncrypted(const std::string& itemPath) const override { return book_->isEncrypted(itemPath); }
|
|
|
|
size_t decryptedSize(const std::string& itemPath) const override { return book_->decryptedSize(itemPath); }
|
|
|
|
bool decryptToSink(const std::string& itemPath, ContentChunkSink sink, void* context) override {
|
|
SdByteSource source(epubPath_);
|
|
if (!source.open()) return false;
|
|
return book_->decryptEntryToSink(source, crypto(), itemPath, sink, context);
|
|
}
|
|
|
|
private:
|
|
std::string epubPath_;
|
|
std::unique_ptr<ProtectedBook> book_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<ContentDecryptor> openProtectedBook(const std::string& epubPath, std::string& err) {
|
|
err.clear();
|
|
if (!Storage.exists(epubPath.c_str())) return nullptr;
|
|
|
|
SdByteSource source(epubPath);
|
|
if (!source.open()) return nullptr;
|
|
|
|
// Classify before initializing crypto or loading credentials, then transfer
|
|
// this same ZIP index into ProtectedBook. Protected EPUBs still scan only
|
|
// once; plain EPUBs return immediately through the normal reader path.
|
|
ZipScan scan;
|
|
if (!scan.open(source) || !scan.find("META-INF/encryption.xml")) return nullptr;
|
|
|
|
// A book carrying encryption.xml may only obfuscate its embedded fonts
|
|
// (not content-protected). The SDK demands the credential only after parsing
|
|
// the manifest and finding genuinely encrypted entries.
|
|
SdByteSource credSource(kCredentialPath);
|
|
Credential credential;
|
|
const bool haveCredential = credSource.open() && parseCredential(credSource, &credential);
|
|
|
|
auto book = makeUniqueNoThrow<ProtectedBook>();
|
|
if (!book) {
|
|
err = "out of memory";
|
|
return nullptr;
|
|
}
|
|
// Prefer an out-of-band rights document delivered as a sidecar next to the
|
|
// EPUB ("<book>.epub.rights"), so the EPUB on disk stays byte-identical to
|
|
// what the server sent. Falls back to a rights.xml injected into the zip.
|
|
std::string rightsOverride;
|
|
{
|
|
// A real rights document is a few KB; 64KB is a generous ceiling. The
|
|
// largest-block check keeps the resize below from aborting on OOM (string
|
|
// growth is a bare allocation under -fno-exceptions).
|
|
constexpr uint64_t kMaxRightsSize = 64 * 1024;
|
|
SdByteSource rightsSource(epubPath + ".rights");
|
|
if (rightsSource.open()) {
|
|
const uint64_t rsize = rightsSource.size();
|
|
if (rsize > 0 && rsize <= kMaxRightsSize &&
|
|
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) > static_cast<size_t>(rsize) + 8 * 1024) {
|
|
rightsOverride.resize(static_cast<size_t>(rsize));
|
|
const int32_t rn = rightsSource.readAt(0, rightsOverride.data(), static_cast<uint32_t>(rsize));
|
|
if (rn <= 0)
|
|
rightsOverride.clear();
|
|
else
|
|
rightsOverride.resize(static_cast<size_t>(rn));
|
|
}
|
|
}
|
|
}
|
|
if (!book->openFromScan(source, crypto(), credential, std::move(scan), rightsOverride)) {
|
|
err = haveCredential ? ("cannot open protected content: " + book->lastError())
|
|
: "no content access key on this device";
|
|
return nullptr;
|
|
}
|
|
// An encryption manifest containing only font obfuscation does not require
|
|
// this read path; let the reader open it normally.
|
|
if (!book->isProtected()) return nullptr;
|
|
|
|
const int64_t now = static_cast<int64_t>(time(nullptr));
|
|
if (book->isExpired(now)) {
|
|
err = "access expired";
|
|
return nullptr;
|
|
}
|
|
|
|
auto decryptor = makeUniqueNoThrow<ProtectedBookDecryptor>(epubPath, std::move(book));
|
|
if (!decryptor) err = "out of memory";
|
|
return decryptor;
|
|
}
|
|
|
|
} // namespace content
|
|
} // namespace freeink
|