* Copyright (c) Huawei Device Co., Ltd. 2026-2026. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <cinttypes>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <securec.h>
#include "alloctable.h"
#include "dirtree.h"
#include "header.h"
#include "storage.h"
#include "stream.h"
#include "utils.h"
namespace OHOS {
namespace ObjectEditor {
namespace {
constexpr int EOF_FLAG = static_cast<unsigned int>(StreamImpl::State::Eof);
constexpr int BAD_FLAG = static_cast<unsigned int>(StreamImpl::State::Bad);
}
const std::string StreamImpl::nullPath;
StreamImpl::StreamImpl(const StreamImpl &stream)
: io_(stream.io_), entry_(stream.entry_), path_(stream.path_), cachedStart_(stream.cachedStart_),
cachedSize_(stream.cachedSize_), cachedUseBig_(stream.cachedUseBig_), blocks_(stream.blocks_), pos_(stream.pos_),
cache_(stream.cache_), cacheSize_(stream.cacheSize_), cachePos_(stream.cachePos_), state_(stream.state_)
{
}
StreamImpl::StreamImpl(StorageIO *io, DirEntry *e) : io_(io), entry_(e), pos_(0), cacheSize_(0), cachePos_(0), state_(0)
{
Init();
}
StreamImpl::StreamImpl(StorageIO *io, const std::string &path)
: io_(io), entry_(io ? io->Entry(path) : nullptr), path_(path), pos_(0), cacheSize_(0), cachePos_(0), state_(0)
{
Init();
}
void StreamImpl::Init()
{
pos_ = 0;
cachePos_ = 0;
cacheSize_ = CACHE_SIZE;
cache_.assign(cacheSize_, 0);
blocks_.clear();
cachedStart_ = DIR_ENTRY_END;
cachedUseBig_ = false;
cachedSize_ = 0;
if (!entry_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry is null");
return;
}
cachedStart_ = entry_->Start();
cachedSize_ = entry_->Size();
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return;
}
cachedUseBig_ = io_->GetHeader() && cachedSize_ >= io_->GetHeader()->Threshold();
if (cachedSize_ == 0) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "cachedSize is 0");
return;
}
if (cachedUseBig_) {
if (!io_->FollowBigBlockTable(cachedStart_, blocks_)) {
state_ = BAD_FLAG;
}
} else if (!io_->FollowSmallBlockTable(cachedStart_, blocks_)) {
state_ = BAD_FLAG;
}
}
bool StreamImpl::PrepareRead(size_t pos, Byte *buffer, std::streamsize maxlen, size_t &allowed, size_t &entrySizeT)
{
if (!entry_ || !buffer || maxlen <= 0) [[unlikely]] {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry, buffer, or maxlen is null");
return false;
}
const StreamPos entrySize = entry_->Size();
entrySizeT = entrySize > static_cast<StreamPos>(std::numeric_limits<size_t>::max()) ?
std::numeric_limits<size_t>::max() : static_cast<size_t>(entrySize);
if (entrySize > 0) {
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return false;
}
const bool useBig = io_->GetHeader() && entrySize >= io_->GetHeader()->Threshold();
if (blocks_.empty() || cachedStart_ != entry_->Start() ||
cachedSize_ != entrySize || cachedUseBig_ != useBig) {
RefreshBlocks();
}
}
if (pos >= entrySizeT) {
state_ |= EOF_FLAG;
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "pos exceed");
return false;
}
allowed = static_cast<size_t>(maxlen);
const size_t available = entrySizeT - pos;
if (allowed > available) {
allowed = available;
state_ |= EOF_FLAG;
} else {
state_ &= ~EOF_FLAG;
}
return allowed > 0;
}
std::streamsize StreamImpl::ReadSmallBlocks(size_t pos, Byte *buffer, size_t allowed)
{
if (!io_ || io_->SmallBlockSize() == 0) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null or SmallBlockSize is 0");
return 0;
}
size_t index = pos / io_->SmallBlockSize();
if (index >= blocks_.size()) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "index exceed blocks size");
return 0;
}
std::vector<Byte> buf(io_->SmallBlockSize());
size_t offset = pos % io_->SmallBlockSize();
std::streamsize totalbytes = 0;
if (allowed > MAX_ALLOWED_2G) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "size is too large");
return 0;
}
while (totalbytes < static_cast<std::streamsize>(allowed)) {
if (index >= blocks_.size()) {
if (totalbytes < static_cast<std::streamsize>(allowed)) {
state_ |= EOF_FLAG;
}
break;
}
const uint32_t read = io_->LoadSmallBlock(blocks_[index], buf.data(), io_->SmallBlockSize());
if (read != io_->SmallBlockSize()) {
state_ |= BAD_FLAG;
break;
}
std::streamsize count = static_cast<std::streamsize>(io_->SmallBlockSize() - offset);
const std::streamsize remaining = static_cast<std::streamsize>(allowed) - totalbytes;
if (count > remaining) {
count = remaining;
}
const size_t destRemaining = allowed - static_cast<size_t>(totalbytes);
if (memcpy_s(buffer + static_cast<size_t>(totalbytes), destRemaining, buf.data() + offset,
static_cast<size_t>(count)) != EOK) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "StreamImpl::ReadSmallBlocks memcpy_s failed");
return 0;
}
totalbytes += count;
offset = 0;
index++;
}
return totalbytes;
}
std::streamsize StreamImpl::ReadBigBlocks(size_t pos, Byte *buffer, size_t allowed, StreamPos entrySize)
{
if (!io_ || io_->BigBlockSize() == 0) {
return 0;
}
size_t index = pos / io_->BigBlockSize();
if (index >= blocks_.size()) {
return 0;
}
if (allowed > MAX_ALLOWED_2G) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "StreamImpl::ReadBigBlocks - exceeds 2 GB");
return 0;
}
std::vector<Byte> buf(io_->BigBlockSize());
size_t offset = pos % io_->BigBlockSize();
std::streamsize totalbytes = 0;
while (totalbytes < static_cast<std::streamsize>(allowed)) {
if (index >= blocks_.size()) {
if (totalbytes < static_cast<std::streamsize>(allowed) &&
pos + static_cast<size_t>(totalbytes) < entrySize) {
state_ |= BAD_FLAG;
}
break;
}
const uint32_t read = io_->LoadBigBlock(blocks_[index], buf.data(), io_->BigBlockSize());
if (read == 0) {
if (pos + static_cast<size_t>(totalbytes) < entrySize) {
state_ |= BAD_FLAG;
}
break;
}
std::streamsize available = static_cast<std::streamsize>(read) - static_cast<std::streamsize>(offset);
std::streamsize count = (available < static_cast<std::streamsize>(allowed) - totalbytes) ?
available : static_cast<std::streamsize>(allowed) - totalbytes;
if (count > 0) {
const size_t destRemaining = allowed - static_cast<size_t>(totalbytes);
if (static_cast<std::streamsize>(destRemaining) < count) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "StreamImpl::ReadBigBlocks - destRemaining < count");
return 0;
}
if (memcpy_s(buffer + static_cast<size_t>(totalbytes), destRemaining, buf.data() + offset,
static_cast<size_t>(count)) != EOK) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "StreamImpl::ReadBigBlocks memcpy_s failed");
return 0;
}
totalbytes += count;
}
if (read < io_->BigBlockSize()) {
break;
}
index++;
offset = 0;
}
return totalbytes;
}
void StreamImpl::RefreshBlocks()
{
blocks_.clear();
cachedStart_ = DIR_ENTRY_END;
cachedSize_ = 0;
cachedUseBig_ = false;
if (!entry_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry is null");
return;
}
cachedStart_ = entry_->Start();
cachedSize_ = entry_->Size();
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return;
}
cachedUseBig_ = io_->GetHeader() && cachedSize_ >= io_->GetHeader()->Threshold();
if (cachedSize_ == 0) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "cachedSize is 0");
return;
}
if (cachedUseBig_) {
bool success = io_->FollowBigBlockTable(cachedStart_, blocks_);
if (!success) {
state_ = BAD_FLAG;
}
} else {
bool success = io_->FollowSmallBlockTable(cachedStart_, blocks_);
if (!success) {
state_ = BAD_FLAG;
}
}
}
int StreamImpl::Getch()
{
if (!entry_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry is null");
return 0;
}
if (pos_ > static_cast<std::streamsize>(entry_->Size())) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "pos exceed");
return -1;
}
if (!cacheSize_ || (pos_ < cachePos_) || (pos_ >= cachePos_ + cacheSize_)) {
UpdateCache();
}
if (!cacheSize_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "cacheSize is 0");
return -1;
}
int data = cache_[pos_ - cachePos_];
pos_++;
return data;
}
std::streamsize StreamImpl::Read(size_t pos, Byte *buffer, std::streamsize maxlen)
{
size_t allowed = 0;
size_t entrySizeT = 0;
if (!PrepareRead(pos, buffer, maxlen, allowed, entrySizeT)) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "PrepareRead failed");
return 0;
}
const StreamPos entrySize = entry_->Size();
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return 0;
}
if (io_->GetHeader() && entrySize < io_->GetHeader()->Threshold()) {
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "less than threshold");
return ReadSmallBlocks(pos, buffer, allowed);
}
return ReadBigBlocks(pos, buffer, allowed, entrySize);
}
std::streamsize StreamImpl::Read(Byte *data, std::streamsize maxlen)
{
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "maxlen: %{public}" PRIu64, static_cast<uint64_t>(maxlen));
std::streamsize bytes = Read(Tell(), data, maxlen);
pos_ += bytes;
if (entry_ && pos_ >= 0 && static_cast<StreamPos>(pos_) == entry_->Size()) {
state_ |= EOF_FLAG;
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "reach EOF, pos: %{public}" PRIu64 ", size: %{public}" PRIu64,
static_cast<uint64_t>(pos_), static_cast<uint64_t>(entry_->Size()));
}
return bytes;
}
std::streamsize StreamImpl::ReadBufferUntilNull(std::vector<Byte> &buffer)
{
Byte buf[CACHE_SIZE];
std::streamsize bytesRead = Read(buf, CACHE_SIZE);
if (bytesRead <= 0) {
return bytesRead;
}
for (int i = 0; i < bytesRead; i++) {
if (buf[i] == '\0') {
break;
}
buffer.push_back(buf[i]);
}
return buffer.size();
}
void StreamImpl::UpdateCache()
{
if (!entry_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry is null");
return;
}
if (cache_.empty()) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "cache is empty");
return;
}
if (cacheSize_ == 0) {
return;
}
cachePos_ = pos_ - (pos_ % cacheSize_);
size_t bytes = static_cast<size_t>(cacheSize_);
const StreamPos entrySize = entry_->Size();
const StreamPos cacheStart = cachePos_ < 0 ? 0 : static_cast<StreamPos>(cachePos_);
if (static_cast<size_t>(cacheStart) + bytes > static_cast<size_t>(entrySize)) {
bytes = static_cast<size_t>(entrySize - cacheStart);
}
cacheSize_ = Read(static_cast<size_t>(cacheStart),
cache_.data(), static_cast<std::streamsize>(bytes));
}
bool StreamImpl::PrepareWrite(const Byte *data, uint32_t &maxlen)
{
if (!entry_ || !data || maxlen == 0) [[unlikely]] {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry, data, or maxlen is null");
return false;
}
const StreamPos entrySize = entry_->Size();
if (entrySize > 0) {
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return false;
}
const Header* header = io_->GetHeader();
const bool useBig = header != nullptr && entrySize >= header->Threshold();
if (blocks_.empty() || cachedStart_ != entry_->Start() ||
cachedSize_ != entrySize || cachedUseBig_ != useBig) {
RefreshBlocks();
}
}
state_ &= ~EOF_FLAG;
return true;
}
bool StreamImpl::EnsureWriteCapacity(uint32_t &targetLen)
{
if (entry_ == nullptr) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "entry is null");
return false;
}
const StreamPos currentPos = pos_ < 0 ? 0 : static_cast<StreamPos>(pos_);
if (targetLen > std::numeric_limits<StreamPos>::max() - currentPos) {
return false;
}
const StreamPos desiredSize = currentPos + static_cast<StreamPos>(targetLen);
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "currentPos:%{public}" PRIu64 ", desiredSize:%{public}" PRIu64,
static_cast<uint64_t>(currentPos), static_cast<uint64_t>(desiredSize));
if (desiredSize <= entry_->Size()) {
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "no need to extend currentSize: %{public}" PRIu64,
static_cast<uint64_t>(entry_->Size()));
return true;
}
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return false;
}
if (!io_->ExtendEntry(entry_, desiredSize)) {
const StreamPos currentSize = entry_->Size();
const StreamPos trimmed = (currentSize > currentPos) ? (currentSize - currentPos) : 0;
targetLen = static_cast<uint32_t>(std::min<StreamPos>(trimmed,
static_cast<StreamPos>(std::numeric_limits<uint32_t>::max())));
state_ |= (EOF_FLAG | BAD_FLAG);
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
"ExtendEntry failed trimmed: %{public}" PRIu64 ", state: %{public}u",
static_cast<uint64_t>(trimmed), state_);
} else {
RefreshBlocks();
}
return targetLen > 0;
}
uint32_t StreamImpl::WriteMiniBlocks(const Byte *data, uint32_t targetLen,
uint64_t smallBlockSize, uint64_t bigBlockSize)
{
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "targetLen: %{public}u, pos: %{public}" PRIu64,
targetLen, static_cast<uint64_t>(pos_));
if (smallBlockSize == 0 || bigBlockSize == 0) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "smallBlockSize or bigBlockSize is 0");
return 0;
}
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return 0;
}
std::vector<uint32_t> sbrootEntry = io_->SbBlocks();
if (pos_ < 0) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "pos is invalid");
return 0;
}
size_t index = static_cast<size_t>(pos_ / static_cast<std::streamsize>(smallBlockSize));
if (index > sbrootEntry.size()) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
"index exceed root Entry size, index: %{public}zu, size: %{public}zu",
index, sbrootEntry.size());
return 0;
}
uint64_t offset = static_cast<uint64_t>(pos_ % static_cast<std::streamsize>(smallBlockSize));
uint32_t count = 0;
for (; ((index < blocks_.size()) && (count < targetLen)); index++) {
const uint32_t minifatIndex = blocks_[index];
const uint64_t sbindexOffset = minifatIndex % 8;
if (static_cast<uint64_t>(minifatIndex) > UINT64_MAX / smallBlockSize) {
state_ |= BAD_FLAG;
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "minifatIndex * smallBlockSize overflow");
return count;
}
const uint64_t position = static_cast<uint64_t>(minifatIndex) * smallBlockSize;
const size_t bbindex = static_cast<size_t>(position / bigBlockSize);
if (bbindex >= sbrootEntry.size()) {
state_ |= BAD_FLAG;
return count;
}
const uint32_t bbindice = sbrootEntry[bbindex];
if (static_cast<uint64_t>(bbindice) > UINT64_MAX / bigBlockSize) {
state_ |= BAD_FLAG;
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "bbindice * bigBlockSize overflow");
return count;
}
uint64_t baseOffset = static_cast<uint64_t>(bbindice) * bigBlockSize;
uint64_t miniOffset = sbindexOffset * smallBlockSize;
if (baseOffset > UINT64_MAX - bigBlockSize - miniOffset - offset) {
state_ |= BAD_FLAG;
return count;
}
const uint64_t physicalOffset = baseOffset + bigBlockSize + miniOffset + offset;
const uint64_t canWrite64 = smallBlockSize - offset;
const uint32_t canWrite =
static_cast<uint32_t>(std::min<uint64_t>(canWrite64, static_cast<uint64_t>(targetLen - count)));
const uint32_t written = io_->SaveBlock(physicalOffset, data + count, canWrite);
if (written < canWrite) {
state_ |= BAD_FLAG;
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
"write block incomplete, expected: %{public}u, actual: %{public}u, physicalOffset: %{public}" PRIu64,
canWrite, written, static_cast<uint64_t>(physicalOffset));
return count;
}
count += written;
offset = 0;
}
return count;
}
uint32_t StreamImpl::WriteBigBlocks(const Byte *data, uint32_t targetLen, uint64_t bigBlockSize)
{
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "targetLen: %{public}u, pos: %{public}" PRIu64,
targetLen, static_cast<uint64_t>(pos_));
if (bigBlockSize == 0) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "bigBlockSize is 0");
return 0;
}
size_t index = static_cast<size_t>(pos_ / static_cast<std::streamsize>(bigBlockSize));
if (index > blocks_.size()) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
"index exceed blocks size, index: %{public}zu, blocks: %{public}zu",
index, blocks_.size());
return 0;
}
uint64_t offset = static_cast<uint64_t>(pos_ % static_cast<std::streamsize>(bigBlockSize));
uint32_t count = 0;
for (; ((index < blocks_.size()) && (count < targetLen)); index++) {
const uint64_t blockOffset = static_cast<uint64_t>(blocks_[index]) * bigBlockSize;
if (blockOffset > UINT64_MAX - bigBlockSize - offset) {
state_ |= BAD_FLAG;
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "physicalOffset overflow in WriteBigBlocks");
return count;
}
const uint64_t physicalOffset = blockOffset + bigBlockSize + offset;
const uint64_t canWrite64 = bigBlockSize - offset;
const uint32_t canWrite = static_cast<uint32_t>(
std::min<uint64_t>(canWrite64, static_cast<uint64_t>(targetLen - count)));
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null in write loop");
return 0;
}
const uint32_t written = io_->SaveBlock(physicalOffset, data + count, canWrite);
if (written < canWrite) {
state_ |= BAD_FLAG;
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
"write block incomplete, expected:%{public}u, actual:%{public}u, physicalOffset:%{public}" PRIu64,
canWrite, written, static_cast<uint64_t>(physicalOffset));
return count;
}
count += written;
offset = 0;
}
return count;
}
uint32_t StreamImpl::Write(const Byte *data, uint32_t maxlen)
{
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "maxlen: %{public}u", maxlen);
if (!PrepareWrite(data, maxlen)) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "PrepareWrite failed");
return 0;
}
if (!EnsureWriteCapacity(maxlen)) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
"EnsureWriteCapacity failed, maxlen: %{public}u, pos: %{public}" PRIu64,
maxlen, static_cast<uint64_t>(pos_));
return 0;
}
if (!io_) {
OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "io is null");
return 0;
}
const uint32_t targetLen = maxlen;
const uint64_t bigBlockSize = io_->BigBlockSize();
const uint64_t smallBlockSize = io_->SmallBlockSize();
uint32_t count = 0;
if (io_->GetHeader() && entry_->Size() < io_->GetHeader()->Threshold()) {
count = WriteMiniBlocks(data, targetLen, smallBlockSize, bigBlockSize);
} else {
count = WriteBigBlocks(data, targetLen, bigBlockSize);
}
if (pos_ > std::numeric_limits<std::streamsize>::max() - count) {
return 0;
}
pos_ += static_cast<std::streamsize>(count);
OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "success count: %{public}u, pos: %{public}" PRIu64,
count, static_cast<uint64_t>(pos_));
return count;
}
}
}