1219 lines
41 KiB
C++
1219 lines
41 KiB
C++
/*
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
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
|
|
|
|
|
|
THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
|
|
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
|
|
TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
|
|
|
|
|
|
See the Apache Version 2.0 License for specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
#include "DrCommon.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <share.h>
|
|
|
|
#pragma unmanaged
|
|
|
|
void DrMemoryStream::DiscardMemoryStreamContext()
|
|
{
|
|
pBlockBase = NULL;
|
|
blockLength = 0;
|
|
pData = NULL;
|
|
uBlockBasePhysicalStreamPosition = 0;
|
|
}
|
|
|
|
DrMemoryStream::DrMemoryStream()
|
|
{
|
|
status = DrError_OK;
|
|
pBlockBase = NULL;
|
|
blockLength = 0;
|
|
pData = NULL;
|
|
uBlockBasePhysicalStreamPosition = 0;
|
|
}
|
|
|
|
DrMemoryStream::~DrMemoryStream()
|
|
{
|
|
DiscardMemoryStreamContext();
|
|
status = DrError_Impossible;
|
|
}
|
|
|
|
__declspec(deprecated) DrError DrMemoryStream::Close()
|
|
{
|
|
DiscardMemoryStreamContext();
|
|
return status;
|
|
}
|
|
|
|
DrMemoryWriter::DrMemoryWriter()
|
|
{
|
|
m_pendingBufferredWriteOldAvailableSize = 0;
|
|
m_fMemoryWriterIsClosed = false;
|
|
m_fIgnoreMemoryWriterCloseFailureInDestructor = false;
|
|
}
|
|
|
|
void DrMemoryWriter::InternalFree()
|
|
{
|
|
m_pPendingBufferedWriteBuffer = NULL;
|
|
m_pendingBufferredWriteOldAvailableSize = 0;
|
|
}
|
|
|
|
// Clears all buffer context values to initial defaults (including settting the physical stream position to 0
|
|
// and discarding abandandoned temporary buffered write buffers), but does not change the current status code
|
|
// or "closed" status.
|
|
// may be overridden by a subclass if it needs to free resources controlled by the
|
|
// context pointers before delegating to this method implementation.
|
|
void DrMemoryWriter::DiscardMemoryWriterContext()
|
|
{
|
|
DrMemoryStream::DiscardMemoryStreamContext();
|
|
InternalFree();
|
|
}
|
|
|
|
DrError DrMemoryWriter::CloseMemoryWriter()
|
|
{
|
|
// Note, if we got here, we were chained to by any subclass implementation of CloseMemoryWriter.
|
|
|
|
// We only allow CloseMemoryWriter to be called once
|
|
if (!m_fMemoryWriterIsClosed)
|
|
{
|
|
// Before we close, we call FlushMemoryWriter.
|
|
// We do not bother flushing if we are in an error condition.
|
|
// This is a virtual method which can be implemented by a subclass.
|
|
if (status == DrError_OK)
|
|
{
|
|
SetStatus(FlushMemoryWriter());
|
|
}
|
|
|
|
// Release any abandoned temporary buffered write buffers, and clear the current contiguous block context.
|
|
// The current status remains unchanged.
|
|
// We do this regardless of status because it frees unneeded resources even on failure.
|
|
DiscardMemoryWriterContext();
|
|
|
|
// We set the closed flag regardless of the status. CloseMemoryWriter can only be called once.
|
|
m_fMemoryWriterIsClosed = true;
|
|
}
|
|
|
|
// At this point the only meaningful state we maintain is the status and the closed flag.
|
|
return status;
|
|
}
|
|
|
|
void DrMemoryWriter::MemoryWriterDestructorClose()
|
|
{
|
|
if (!m_fMemoryWriterIsClosed)
|
|
{
|
|
// If status is already failing, then it is OK to fail close at destructor time, since no new error
|
|
// is occurring, so flushing is unnecessary.
|
|
bool fIgnoreFailures = (m_fIgnoreMemoryWriterCloseFailureInDestructor || status != DrError_OK);
|
|
|
|
// We call CloseMemoryWriter even if status is not DrError_OK because implementations
|
|
// use CloseMemoryWriter to free resources even in an error state.
|
|
// NOTE that this call goes through the virtual memory chain, executing on the most-derived class first.
|
|
SetStatus(CloseMemoryWriter());
|
|
|
|
// if m_fMemoryWriterIsClosed is not set, it means that someone forgot to chain to their parent class's
|
|
// CloseMemoryWriter()
|
|
LogAssert(m_fMemoryWriterIsClosed);
|
|
|
|
// If a new error occured on close and we are not ignoring errors, then it is a fatal error.
|
|
LogAssert(fIgnoreFailures || status == DrError_OK);
|
|
}
|
|
}
|
|
|
|
DrMemoryWriter::~DrMemoryWriter()
|
|
{
|
|
//=======================================================================================================
|
|
// NOTE: each subclass should duplicate this code if it implements CloseMemoryWriter or FlushMemoryWriter
|
|
//
|
|
if (!MemoryWriterIsClosed())
|
|
{
|
|
// Close the memory writer. We cannot tolerate new failures in CloseMemoryWriter at this point because we are in a destructor!
|
|
// This will call FlushMemoryWriter() before closing, but because of virtual destructor unwinding, it will call the
|
|
// current class's implementation rather than the subclass's overriding implementation. So each subclass needs to implement this call in their
|
|
// virtual destructor if they implement CloseMemoryWriter or FlushMemoryWriter
|
|
MemoryWriterDestructorClose();
|
|
}
|
|
//
|
|
// NOTE: End dublicated destructor code
|
|
//=======================================================================================================
|
|
|
|
InternalFree();
|
|
}
|
|
|
|
// Write a narrow (char[]) string "pstr" as a property, with an explicit provided string length. pstr may be NULL, which is
|
|
// properly encoded as a distinct value from a zero-length string. Typically, a non-NULL pstr points to a
|
|
// UTF-8 string (not-necessarily '\0'-terminated) that is "length" bytes long, without any embedded '\0' bytes;
|
|
// however, this function may be used to encode arbitrary byte blocks, including blocks that contain embedded '\0' bytes,
|
|
// so it can be used, e.g., to encode a concatenated list of '\0'-terminated strings.
|
|
// The primary distinctions between this function and WriteBlob are:
|
|
// a) NULL is a distinct value from a zero-length string.
|
|
// b) The property length is 0 for NULL values, and Llength"+1 for non-NULL values.
|
|
// b) For non-NULL values, a '\0' is appended to the string bytes as the last byte of the property data. This does not change
|
|
// the value of the string, but allows a reader to use the serialized data as a null terminated string without copying it, and
|
|
// allows a reader to distinguish between a NULL string and an empty string.
|
|
//
|
|
// The provided length does not include the terminating '\0' byte; however, a non-NULL pstr
|
|
// is always '\0'-terminated in the output stream, and a NULL pstr is wriiten as an empty property.
|
|
// The actual length of the property data will be "length" + 1 if pstr is not NULL.
|
|
//
|
|
// These semantics are consistent with round-trip encoding of a DrStr value.
|
|
//
|
|
// This vertion only works with LONGATOM properties. This is asserted, to help detect bugs where strings that may
|
|
// occasionally exceed 254 bytes are accidently tagged as SHORTATOM. If you have a string property that you know
|
|
// *must* always be less than 255 bytes long, you can use WriteShortStringPropertyWithLength.
|
|
//
|
|
// Causes an assertion failure if:
|
|
// a) enumId is a SHORTATOM
|
|
// b) length >= _UI32_MAX
|
|
// c) pstr == NULL && length != 0
|
|
DrError DrMemoryWriter::WriteLongStringPropertyWithLength(UInt16 enumId, const char *pstr, Size_t length)
|
|
{
|
|
if (pstr == NULL)
|
|
{
|
|
LogAssert(length == 0);
|
|
WriteEmptyLongProperty(enumId);
|
|
}
|
|
else
|
|
{
|
|
LogAssert(length < (Size_t)_UI32_MAX);
|
|
if (WritePropertyTagLong(enumId, length + 1) == DrError_OK)
|
|
{
|
|
WriteBytes(pstr, length);
|
|
WriteChar('\0');
|
|
}
|
|
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// This version writes a SHORTATOM string value.
|
|
//
|
|
// This vertion only works with SHORTATOM properties. If you have a string property that you know
|
|
// *must* always be less than 255 bytes long, you can use a SHORTATOM property ID and this method.
|
|
// Otherwise, you should use WriteLongStringPropertyWithLength().
|
|
//
|
|
// Causes an assertion failure if:
|
|
// a) enumId is a LONGATOM
|
|
// b) length >= 255
|
|
// c) pstr == NULL && length != 0
|
|
DrError DrMemoryWriter::WriteShortStringPropertyWithLength(UInt16 enumId, const char *pstr, Size_t length)
|
|
{
|
|
if (pstr == NULL)
|
|
{
|
|
LogAssert(length == 0);
|
|
WriteEmptyShortProperty(enumId);
|
|
}
|
|
else
|
|
{
|
|
LogAssert(length < (Size_t)_UI8_MAX);
|
|
if (WritePropertyTagShort(enumId, length + 1) == DrError_OK)
|
|
{
|
|
WriteBytes(pstr, length);
|
|
WriteChar('\0');
|
|
}
|
|
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// This method should be overridden by memory writers that know how to allocate a new block and keep writing.
|
|
// The implementation should update pData, pBlockBase, blockLength, and uBlockBasePhysicalStreamPosition to point to the new block.
|
|
// The current block will be completely filled before calling this method, since there is no way to back up.
|
|
// After this call, the old block can be disposed of in any way the underlying implementation chooses (e.g., flushing).
|
|
// Returns DrError_EndOfStream if a new block can't or shouldn't be allocated.
|
|
// The default implementation always returns DrError_EndOfStream, which is appropriate for single-block writers.
|
|
// If an error is returned, status has been set.
|
|
DrError DrMemoryWriter::AdvanceToNextBlock()
|
|
{
|
|
return SetStatus(DrError_EndOfStream);
|
|
}
|
|
|
|
// This method checks whether an attempt to write beyond the end of the current block
|
|
// will succeed. The implementation should return true if the buffer is indefinitely growable.
|
|
// The default implementation returns false, which is appropriate for single-block writers.
|
|
// NOt that this method does not set or check status
|
|
bool DrMemoryWriter::FutureBlocksCanBeWritten(size_t length)
|
|
{
|
|
(void)length;
|
|
return false;
|
|
}
|
|
|
|
// This method writes bytes into the buffer, handling the case where the data
|
|
// will cross blocks.
|
|
DrError DrMemoryWriter::CrossBlockWriteBytes(const BYTE *pBytes, size_t length)
|
|
{
|
|
if (EnsureCanBeWritten(length) != DrError_OK) {
|
|
return status;
|
|
}
|
|
|
|
while (length > (size_t)0) {
|
|
if (NumContiguousBytesRemaining() == (size_t)0) {
|
|
if (AdvanceToNextBlock() != DrError_OK) {
|
|
return status;
|
|
}
|
|
}
|
|
size_t nr = NumContiguousBytesRemaining();
|
|
LogAssert(nr > 0);
|
|
size_t nb = (length > nr) ? nr : length;
|
|
memcpy(pData, pBytes, nb);
|
|
pData += nb;
|
|
pBytes += nb;
|
|
length -= nb;
|
|
}
|
|
return DrError_OK;
|
|
}
|
|
|
|
DrError DrMemoryWriter::WriteBytesFromReader(DrMemoryReader *pReader, Size_t length)
|
|
{
|
|
SetStatus(pReader->ReadBytesIntoWriter(this, length));
|
|
return status;
|
|
}
|
|
|
|
DrError DrMemoryWriter::WriteBytesFromBuffer(DrMemoryBuffer *pBuffer, bool fAllowCopyBufferByReference)
|
|
{
|
|
if (status == DrError_OK) {
|
|
Size_t nb = pBuffer->GetAvailableSize();
|
|
if (nb != 0) {
|
|
DrMemoryBufferReader reader(pBuffer, fAllowCopyBufferByReference);
|
|
WriteBytesFromReader(&reader, nb);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
// DrMemoryReader
|
|
|
|
DrMemoryReader::DrMemoryReader()
|
|
{
|
|
pFirstTempMemHeader = NULL;
|
|
pFirstPeekMemHeader = NULL;
|
|
pLastPeekMemHeader = NULL;
|
|
fAllowCopyBufferByReference = false;
|
|
m_fMemoryReaderIsClosed = false;
|
|
}
|
|
|
|
void DrMemoryReader::InternalFree()
|
|
{
|
|
DiscardTemporaryResults();
|
|
DiscardPeekBlocks();
|
|
}
|
|
|
|
// Clears all buffer context values to initial defaults (including settting the physical stream position to 0
|
|
// and discarding peekahead buffers), but does not discard temporary results and does not change the current status code
|
|
// or "closed" status.
|
|
// may be overridden by a subclass if it needs to free resources controlled by the
|
|
// context pointers before delegating to this method implementation.
|
|
void DrMemoryReader::DiscardMemoryReaderContext()
|
|
{
|
|
DrMemoryStream::DiscardMemoryStreamContext();
|
|
DiscardPeekBlocks();
|
|
}
|
|
|
|
// This method clears all buffer context values to initial defaults (including settting the physical stream position to 0,
|
|
// discarding peekahead buffers and temporary results, clearing the "closed" state, and setting the status to DrError_OK.
|
|
// a subclass may override this to reset its own state along with forwarding the request to this class.
|
|
// Calls through the virtual DiscardMemoryReaderContext() before clearing the status and the closed flag.
|
|
void DrMemoryReader::ResetMemoryReader()
|
|
{
|
|
DiscardMemoryReaderContext();
|
|
DiscardTemporaryResults();
|
|
status = DrError_OK;
|
|
m_fMemoryReaderIsClosed = false;
|
|
}
|
|
|
|
DrError DrMemoryReader::CloseMemoryReader()
|
|
{
|
|
// Note, if we got here, we were chained to by any subclass implementation of CloseMemoryReader.
|
|
|
|
// We only allow CloseMemoryReader to be called once
|
|
if (!m_fMemoryReaderIsClosed)
|
|
{
|
|
// Release any abandoned temporary buffered write buffers, and clear the current contiguous block context.
|
|
// The current status remains unchanged.
|
|
// We do this regardless of status because it frees unneeded resources even on failure.
|
|
DiscardMemoryReaderContext();
|
|
DiscardTemporaryResults();
|
|
|
|
// We set the closed flag regardless of the status. CloseMemoryReader can only be called once.
|
|
m_fMemoryReaderIsClosed = true;
|
|
}
|
|
|
|
// At this point the only meaningful state we maintain is the status and the closed flag.
|
|
return status;
|
|
}
|
|
|
|
void DrMemoryReader::MemoryReaderDestructorClose()
|
|
{
|
|
if (!m_fMemoryReaderIsClosed)
|
|
{
|
|
bool fAlreadyFailing = (status != DrError_OK);
|
|
|
|
// We call CloseMemoryReader even if status is not DrError_OK because implementations
|
|
// use CloseMemoryReader to free resources even in an error state.
|
|
// NOTE that this call goes through the virtual memory chain, executing on the most-derived class first.
|
|
SetStatus(CloseMemoryReader());
|
|
|
|
// if m_fMemoryWriterIsClosed is not set, it means that someone forgot to chain to their parent class's
|
|
// CloseMemoryWriter()
|
|
LogAssert(m_fMemoryReaderIsClosed);
|
|
|
|
if (!fAlreadyFailing && (status != DrError_OK))
|
|
{
|
|
// CloseMemoryReader introduced a new failure. But we are in a destructor, so we can't return an error code.
|
|
// Close errors on reader streams are generally hamless, so we will ignore the error.
|
|
;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrMemoryReader::DiscardTemporaryResults()
|
|
{
|
|
while (pFirstTempMemHeader != NULL) {
|
|
TempMemHeader *p = pFirstTempMemHeader;
|
|
pFirstTempMemHeader = p->Detach();
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
void DrMemoryReader::DiscardPeekBlocks()
|
|
{
|
|
while (pFirstPeekMemHeader != NULL) {
|
|
PeekMemHeader *p = pFirstPeekMemHeader;
|
|
pFirstPeekMemHeader = p->Detach();
|
|
delete p;
|
|
}
|
|
pLastPeekMemHeader = NULL;
|
|
}
|
|
|
|
// The destructor for DrMemoryReader frees all the temporary
|
|
// return values allocated since the reader was created.
|
|
DrMemoryReader::~DrMemoryReader()
|
|
{
|
|
//=======================================================================================================
|
|
// NOTE: each subclass should duplicate this code if it implements CloseMemoryReader and needs to have it
|
|
// called at destruct time
|
|
//
|
|
if (!MemoryReaderIsClosed())
|
|
{
|
|
// Close the memory reader. This will call CloseMemoryReader() before closing, but because of virtual destructor unwinding, it will call the
|
|
// current class's implementation rather than the subclass's overriding implementation. So each subclass needs to implement this call in their
|
|
// virtual destructor if they implement CloseMemoryReader.
|
|
MemoryReaderDestructorClose();
|
|
}
|
|
//
|
|
// NOTE: End dublicated destructor code
|
|
//=======================================================================================================
|
|
|
|
InternalFree();
|
|
}
|
|
|
|
|
|
// ReadNextPropertyTag. Reads the next property from the bag, along
|
|
// with its length (either 1- or 4-byte depending on the length
|
|
// bit in the property name). Returns DrError_EndOfStream if there is not enough
|
|
// data in the bag to read out the property name and length, but
|
|
// does not check that there are *pDataLen more bytes remaining.
|
|
DrError DrMemoryReader::ReadNextPropertyTag(
|
|
/* out */ UInt16 *pEnumId,
|
|
/* out */ UInt32 *pDataLen)
|
|
{
|
|
if (ReadUInt16(pEnumId) != DrError_OK) {
|
|
return status;
|
|
}
|
|
|
|
if (((*pEnumId) & PropLengthMask) == PropLength_Short) {
|
|
UInt8 lengthByte;
|
|
if (ReadUInt8(&lengthByte) == DrError_OK) {
|
|
*pDataLen = lengthByte;
|
|
}
|
|
} else {
|
|
ReadUInt32(pDataLen);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// PeekNextPropertyTag. Peeks at the next property from the bag, along
|
|
// with its length (either 1- or 4-byte depending on the length
|
|
// bit in the property name). Returns DrError_EndOfStream if there is not enough
|
|
// data in the bag to read out the property name and length, but
|
|
// does not check that there are *pDataLen more bytes remaining.
|
|
DrError DrMemoryReader::PeekNextPropertyTag(
|
|
/* out */ UInt16 *pEnumId,
|
|
/* out */ UInt32 *pDataLen)
|
|
{
|
|
if (PeekUInt16(pEnumId) != DrError_OK) {
|
|
return status;
|
|
}
|
|
|
|
BYTE tmp[sizeof(UInt16) + sizeof(UInt32)];
|
|
|
|
if (((*pEnumId) & PropLengthMask) == PropLength_Short) {
|
|
if (PeekBytes(tmp, sizeof(UInt16) + sizeof(UInt8)) == DrError_OK) {
|
|
*pDataLen = tmp[sizeof(UInt16)];
|
|
}
|
|
} else {
|
|
if (PeekBytes(tmp, sizeof(UInt16) + sizeof(UInt32)) == DrError_OK) {
|
|
memcpy(pDataLen, tmp+sizeof(UInt16), sizeof(UInt32));
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// PeekNextProperty: Peeks at the next property in the bag and fills
|
|
// in its name and length to pEnumId and pDataLen respectively,
|
|
// not advancing the the read pointer.
|
|
// Returns a pointer to the contiguous property value (either in
|
|
// the buffer or copied to make it contiguous).If PeekNextProperty returns an error,
|
|
// the values of *pEnumId,
|
|
// *pDataLen and *data are undefined.
|
|
DrError DrMemoryReader::PeekNextProperty(
|
|
/* out */ UInt16 *pEnumId,
|
|
/* out */ UInt32 *pDataLen,
|
|
/* out */ const void **data)
|
|
{
|
|
if (PeekNextPropertyTag(pEnumId, pDataLen) == DrError_OK) {
|
|
UInt32 hdrLen = sizeof(UInt16);
|
|
if (((*pEnumId) & PropLengthMask) == PropLength_Short) {
|
|
hdrLen += sizeof(UInt8);
|
|
} else {
|
|
hdrLen += sizeof(UInt32);
|
|
}
|
|
const BYTE *pProp;
|
|
if (PeekBytes(hdrLen + *pDataLen, &pProp) == DrError_OK) {
|
|
*data = pProp + hdrLen;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
// ReadNextKnownProperty: Reads the next property which is of
|
|
// known ID and length into a preallocated buffer, placing the read pointer
|
|
// after the property value. If ReadNextKnownProperty returns error,
|
|
// the position of the read pointer is undefined.
|
|
// returns DrError_InvalidProperty if the enum id or length don't match.
|
|
DrError DrMemoryReader::ReadNextKnownProperty(
|
|
UInt16 enumId,
|
|
UInt32 dataLen,
|
|
void *pDest)
|
|
{
|
|
UInt16 realEnumId;
|
|
UInt32 realDataLen;
|
|
|
|
if (ReadNextPropertyTag(&realEnumId, &realDataLen) == DrError_OK) {
|
|
if (realEnumId != enumId || realDataLen != dataLen) {
|
|
SetStatus(DrError_InvalidProperty);
|
|
} else {
|
|
ReadData(dataLen, pDest);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// PeekNextKnownProperty: Peeks at the next property which is of
|
|
// known ID and length into a preallocated buffer.
|
|
// returns DrError_InvalidProperty if the enum id or length don't match.
|
|
DrError DrMemoryReader::PeekNextKnownProperty(
|
|
UInt16 enumId,
|
|
UInt32 dataLen,
|
|
void *pDest)
|
|
{
|
|
UInt16 enumIdActual;
|
|
UInt32 dataLenActual;
|
|
if (PeekNextPropertyTag(&enumIdActual, &dataLenActual) == DrError_OK) {
|
|
if (enumIdActual != enumId || dataLenActual != dataLen) {
|
|
SetStatus(DrError_InvalidProperty);
|
|
} else {
|
|
UInt32 hdrLen = sizeof(UInt16);
|
|
if ((enumId & PropLengthMask) == PropLength_Short) {
|
|
hdrLen += sizeof(UInt8);
|
|
} else {
|
|
hdrLen += sizeof(UInt32);
|
|
}
|
|
const BYTE *pProp;
|
|
if (PeekBytes(hdrLen + dataLen, &pProp) == DrError_OK) {
|
|
memcpy(pDest, pProp + hdrLen, dataLen);
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
// Reads a string property that has been encoded with WriteStringProperty.
|
|
// If the string in the stream is longer than maxLength (not including null), DrError_StringTooLong is returned
|
|
DrError DrMemoryReader::ReadNextStringProperty(UInt16 enumId, /* out */ const char **ppStr, Size_t maxLength)
|
|
{
|
|
UInt32 length;
|
|
UInt16 realEnumId;
|
|
|
|
if (ReadNextPropertyTag(&realEnumId, &length) == DrError_OK) {
|
|
if (realEnumId != enumId) {
|
|
SetStatus(DrError_InvalidProperty);
|
|
} else if ((Size_t)length > maxLength) {
|
|
SetStatus(DrError_StringTooLong);
|
|
} else {
|
|
*ppStr = NULL;
|
|
|
|
if (length > 0) {
|
|
BYTE *pBytes;
|
|
if (length > NumContiguousBytesRemaining() || pData[length-1] != (BYTE)0) {
|
|
// Must allocate temporary buffer wth enough room for null terminator
|
|
pBytes = ReserveTempMemory(length+1);
|
|
if (ReadBytes(pBytes, length) == DrError_OK) {
|
|
pBytes[length] = (BYTE)0;
|
|
}
|
|
} else {
|
|
ReadBytes(length, (const BYTE **)&pBytes);
|
|
}
|
|
|
|
if (status == DrError_OK) {
|
|
*ppStr = (const char *)(const void *)pBytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Reads a string property that has been encoded with WriteStringProperty.
|
|
// If the string in the stream is longer than maxLength (not including null), DrError_StringTooLong is returned
|
|
DrError DrMemoryReader::ReadOrAppendNextStringProperty(bool fAppend, UInt16 enumId, /* out */ DrStr& strOut, Size_t maxLength)
|
|
{
|
|
UInt32 length;
|
|
UInt16 realEnumId;
|
|
|
|
if (ReadNextPropertyTag(&realEnumId, &length) == DrError_OK) {
|
|
if (realEnumId != enumId) {
|
|
SetStatus(DrError_InvalidProperty);
|
|
} else if (length == 0) {
|
|
// null string
|
|
if (fAppend) {
|
|
strOut.EnsureNotNull();
|
|
} else {
|
|
strOut = NULLSTR;
|
|
}
|
|
} else if ((Size_t)(length) > maxLength) {
|
|
SetStatus(DrError_StringTooLong);
|
|
} else {
|
|
strOut.EnsureNotNull();
|
|
size_t oldlen = strOut.GetLength();
|
|
char *pOut = strOut.GetWritableAppendBuffer((size_t)length);
|
|
ReadBytes((BYTE *)pOut, (Size_t)length);
|
|
|
|
if (status == DrError_OK) {
|
|
strOut.UpdateLength(oldlen + strlen(pOut));
|
|
} else {
|
|
strOut.UpdateLength(oldlen);
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Read a string property from the buffer into a preallocated buffer.
|
|
|
|
If the embedded string is NULL, an empty string is returned.
|
|
|
|
If the string in the stream is longer than buffLength (including null), DrError_StringTooLong is returned
|
|
*/
|
|
DrError DrMemoryReader::ReadNextStringProperty(UInt16 enumId, char *pStr, Size_t buffLength)
|
|
{
|
|
UInt32 length;
|
|
UInt16 realEnumId;
|
|
|
|
if (ReadNextPropertyTag(&realEnumId, &length) == DrError_OK) {
|
|
if (realEnumId != enumId) {
|
|
SetStatus(DrError_InvalidProperty);
|
|
} else if (buffLength == 0) {
|
|
SetStatus(DrError_StringTooLong);
|
|
} else if (length == 0) {
|
|
// NULL string
|
|
pStr[0] = '\0';
|
|
} else if ((Size_t)(length) > buffLength) {
|
|
// We allow reading 1 more byte than maxLength, hoping that the last byte is a null
|
|
SetStatus(DrError_StringTooLong);
|
|
} else {
|
|
pStr[length-1] = '\0';
|
|
ReadBytes((BYTE *)pStr, length);
|
|
|
|
if (status == DrError_OK) {
|
|
// If we read more than buffLength-1 bytes, the last byte has to be a null
|
|
if ((Size_t)length >= buffLength) {
|
|
if (pStr[length-1] != '\0') {
|
|
pStr[0] = '\0';
|
|
SetStatus(DrError_StringTooLong);
|
|
}
|
|
} else {
|
|
pStr[length] = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
void DrMemoryReader::AllocTempMemBlock(size_t minLength)
|
|
{
|
|
if (minLength < DEFAULT_TEMP_MEM_ALLOC_SIZE) {
|
|
minLength = DEFAULT_TEMP_MEM_ALLOC_SIZE;
|
|
}
|
|
|
|
pFirstTempMemHeader = TempMemHeader::Alloc(minLength, pFirstTempMemHeader);
|
|
LogAssert(pFirstTempMemHeader != NULL);
|
|
}
|
|
|
|
// Reserves a block of temporary memory that will be valid until this DrMemoryReader
|
|
// is destroyed.
|
|
BYTE *DrMemoryReader::ReserveTempMemory(size_t length)
|
|
{
|
|
if (pFirstTempMemHeader == NULL || pFirstTempMemHeader->GetLength() < length) {
|
|
AllocTempMemBlock(length);
|
|
LogAssert (pFirstTempMemHeader != NULL && pFirstTempMemHeader->GetLength() >= length);
|
|
}
|
|
return pFirstTempMemHeader->ReserveData(length);
|
|
}
|
|
|
|
// Reads data from blocks starting *after* the current block, without advancing the current read pointer.
|
|
// Returns DrError_EndOfStream if the stream reaches the end before all data can be read (partial data
|
|
// may still be written into the byte array).
|
|
DrError DrMemoryReader::FutureBlockPeekBytes(/* out */ void *pBytes, size_t length)
|
|
{
|
|
(void)pBytes;
|
|
(void)length;
|
|
return SetStatus(DrError_EndOfStream);
|
|
}
|
|
|
|
// Reads data from memory without advancing the current read pointer.
|
|
// Handles cross-block cases.
|
|
// Returns DrError_EndOfStream if the stream reaches the end before all data can be read (partial data
|
|
// is still wriiten into the byte array).
|
|
DrError DrMemoryReader::CrossBlockPeekBytes(/* out */ BYTE *pBytes, size_t length)
|
|
{
|
|
Size_t nb;
|
|
PeekMemHeader *ph;
|
|
const BYTE *pb;
|
|
|
|
if (status == DrError_OK && length > (size_t)0) {
|
|
if (pFirstPeekMemHeader != NULL) {
|
|
ph = pFirstPeekMemHeader;
|
|
pb = pData;
|
|
nb = NumContiguousBytesRemaining();
|
|
|
|
do {
|
|
if (nb > length) {
|
|
nb = length;
|
|
}
|
|
|
|
if (nb > 0) {
|
|
memcpy(pBytes, pb, nb);
|
|
pBytes += nb;
|
|
length -= nb;
|
|
}
|
|
|
|
if (length == 0) {
|
|
break;
|
|
}
|
|
|
|
ph = ph->GetNext();
|
|
if (ph != NULL) {
|
|
pb = ph->GetData();
|
|
nb = ph->GetLength();
|
|
}
|
|
} while (ph != NULL);
|
|
}
|
|
|
|
if (length > 0) {
|
|
if (FutureBlockPeekBytes(pBytes, length) == DrError_PeekTooFar) {
|
|
// stream doesn't support peeking
|
|
while (length > 0 && AppendNextBlock() == DrError_OK) {
|
|
ph = pLastPeekMemHeader;
|
|
pb = ph->GetData();
|
|
nb = ph->GetLength();
|
|
if (nb > length) {
|
|
nb = length;
|
|
}
|
|
|
|
if (nb > 0) {
|
|
memcpy(pBytes, pb, nb);
|
|
pBytes += nb;
|
|
length -= nb;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// Reads data from memory, advancing the current read pointer.
|
|
// Handles cross-block cases.
|
|
// Returns DrError_EndOfStream if the stream reaches the end before all data can be read (partial data
|
|
// is still wriiten into the byte array).
|
|
DrError DrMemoryReader::CrossBlockReadBytes(/* out */ BYTE *pBytes, size_t length)
|
|
{
|
|
while (length > (size_t)0) {
|
|
size_t nr = NumContiguousBytesRemaining();
|
|
if (nr == (size_t)0) {
|
|
DrError ret = AdvanceToNextPeekBlock();
|
|
if (ret != DrError_OK) {
|
|
return ret;
|
|
}
|
|
nr = NumContiguousBytesRemaining();
|
|
}
|
|
LogAssert(nr > 0);
|
|
|
|
size_t nb = (length > nr) ? nr : length;
|
|
memcpy(pBytes, pData, nb);
|
|
pData += nb;
|
|
pBytes += nb;
|
|
length -= nb;
|
|
}
|
|
return DrError_OK;
|
|
}
|
|
|
|
DrError DrMemoryReader::ReadBytesIntoWriter(DrMemoryWriter *pWriter, Size_t length)
|
|
{
|
|
DrError ret;
|
|
while (length > (size_t)0) {
|
|
size_t nr = NumContiguousBytesRemaining();
|
|
if (nr == (size_t)0) {
|
|
ret = AdvanceToNextPeekBlock();
|
|
if (ret != DrError_OK) {
|
|
return ret;
|
|
}
|
|
nr = NumContiguousBytesRemaining();
|
|
}
|
|
LogAssert(nr > 0);
|
|
|
|
size_t nb = (length > nr) ? nr : length;
|
|
ret = pWriter->WriteBytes(pData, nb);
|
|
if (ret != DrError_OK) {
|
|
return SetStatus(ret);
|
|
}
|
|
pData += nb;
|
|
length -= nb;
|
|
}
|
|
return DrError_OK;
|
|
}
|
|
|
|
// Skips data in memory, advancing the current read pointer.
|
|
// Handles cross-block cases.
|
|
// Returns DrError_EndOfStream if the stream reaches the end before all data can be skipped (partial data
|
|
// is still skipped).
|
|
DrError DrMemoryReader::CrossBlockSkipBytes(size_t length)
|
|
{
|
|
while (length > (size_t)0) {
|
|
size_t nr = NumContiguousBytesRemaining();
|
|
if (nr == (size_t)0) {
|
|
DrError ret = AdvanceToNextPeekBlock();
|
|
if (ret != DrError_OK) {
|
|
return ret;
|
|
}
|
|
nr = NumContiguousBytesRemaining();
|
|
}
|
|
LogAssert(nr > 0);
|
|
size_t nb = (length > nr) ? nr : length;
|
|
pData += nb;
|
|
length -= nb;
|
|
}
|
|
return DrError_OK;
|
|
}
|
|
|
|
// Appends the next block from the underlying stream to the list of peekable data blocks.
|
|
DrError DrMemoryReader::AppendNextBlock()
|
|
{
|
|
const BYTE *pBytes;
|
|
Size_t length;
|
|
|
|
if (status == DrError_OK) {
|
|
// Read the next block from the stream
|
|
if (SetStatus(ReadNextBlock(&pBytes, &length)) == DrError_OK) {
|
|
// We have a new block. Append it to the end of the list of peek blocks.
|
|
PeekMemHeader *ph;
|
|
ph = new PeekMemHeader(length, pLastPeekMemHeader, pBytes);
|
|
LogAssert(ph != NULL);
|
|
pLastPeekMemHeader = ph;
|
|
if (pFirstPeekMemHeader == NULL) {
|
|
// We are appending the very first peek block.
|
|
pFirstPeekMemHeader = ph;
|
|
|
|
// The new block is now the current block.
|
|
// We need to update the base class current block pointers
|
|
pData = pBlockBase = pFirstPeekMemHeader->GetData();
|
|
blockLength = pFirstPeekMemHeader->GetLength();
|
|
|
|
// Since this is the first peek block, the prior block was empty and there is
|
|
// no effect on uBlockBasePhysicalStreamPosition
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// Advances the current block to the next available peek block, reading a new block if necessary
|
|
DrError DrMemoryReader::AdvanceToNextPeekBlock()
|
|
{
|
|
if (status == DrError_OK) {
|
|
// remove current peek block, if any
|
|
if (pFirstPeekMemHeader != NULL) {
|
|
// We have at least one peek block. The first one is the "current" block. We need to remove it.
|
|
PeekMemHeader *ph = pFirstPeekMemHeader;
|
|
pFirstPeekMemHeader = ph->Detach();
|
|
if (pFirstPeekMemHeader == NULL) {
|
|
// we removed the last peek block. we now have no current block.
|
|
pLastPeekMemHeader = NULL;
|
|
pData = pBlockBase = NULL;
|
|
blockLength = 0;
|
|
} else {
|
|
// the next peek block is now the current block
|
|
pData = pBlockBase = pFirstPeekMemHeader->GetData();
|
|
blockLength = pFirstPeekMemHeader->GetLength();
|
|
}
|
|
// Since we advanced to a new current block, we need to advance uBlockBasePhysicalStreamPosition
|
|
// by the length of the previous current block
|
|
uBlockBasePhysicalStreamPosition += ph->GetLength();
|
|
delete ph;
|
|
}
|
|
|
|
if (pFirstPeekMemHeader == NULL) {
|
|
// if no more peek blocks, add a new one
|
|
AppendNextBlock();
|
|
}
|
|
|
|
if (status == DrError_OK) {
|
|
LogAssert(pFirstPeekMemHeader != NULL);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
// This method should be overidden by memory readers that know how to advance to a new block.
|
|
// Returns DrError_EndOfStream if there are no more blocks to be read.
|
|
// The default implementation always returns DrError_EndOfStream, which is appropriate for
|
|
// single-block readers.
|
|
DrError DrMemoryReader::ReadNextBlock(/* out */ const BYTE **pBytes, /* out */ Size_t *pLength)
|
|
{
|
|
return SetStatus(DrError_EndOfStream);
|
|
}
|
|
|
|
// This method should be overridden by memory readers that know how to advance to a new block.
|
|
// The implementation should return true if there are at least "length" readable bytes
|
|
// following the current block.
|
|
//
|
|
// The default implementation always returns false, which is appropriate for single-block readers
|
|
bool DrMemoryReader::FutureBlocksCanBeRead(size_t length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// Consumes the (BeginTag, desiredTagType) property and closing (EndTag, desiredTagType) property, and calls you back
|
|
// on parser->OnParseProperty() for each decoded property. Each property it calls you back on has only been peeked,
|
|
// so you will need to read or skip over it. If another BeginTag appears, you will be called back with that.
|
|
DrError DrMemoryReader::ReadAggregate(UInt16 desiredTagType, DrPropertyParser *parser, void *cookie)
|
|
{
|
|
DrError err;
|
|
UInt16 beginTagType;
|
|
|
|
if (ReadNextKnownProperty(Prop_Dryad_BeginTag, sizeof(UInt16), &beginTagType) != DrError_OK)
|
|
return status;
|
|
|
|
if (beginTagType != desiredTagType)
|
|
return SetStatus(DrError_InvalidProperty);
|
|
|
|
while (TRUE)
|
|
{
|
|
UInt16 propertyType;
|
|
UInt32 dataLen;
|
|
|
|
if (PeekNextPropertyTag(&propertyType, &dataLen) != DrError_OK)
|
|
return status;
|
|
|
|
// If we find an end tag, it must be for the begin tag we consumed
|
|
if (propertyType == Prop_Dryad_EndTag)
|
|
{
|
|
UInt16 endTagType;
|
|
|
|
// Consume it
|
|
if (ReadNextUInt16Property(Prop_Dryad_EndTag, &endTagType) != DrError_OK)
|
|
return status;
|
|
|
|
if (desiredTagType != endTagType)
|
|
return SetStatus(DrError_InvalidProperty);
|
|
|
|
// We're done
|
|
return DrError_OK;
|
|
}
|
|
else
|
|
{
|
|
// This could be a begin tag - it's up to the caller to call ReadAggregate()
|
|
// or SkipNextPropertyOrAggregate()
|
|
err = parser->OnParseProperty(this, propertyType, dataLen, cookie);
|
|
if (err != DrError_OK)
|
|
return SetStatus(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the next property is not a BeginTag, it simply skips it.
|
|
// If the next property is a BeginTag, then it skips everything through and including the EndTag,
|
|
// and handles recursion
|
|
// @TODO Limit recursion depth
|
|
DrError DrMemoryReader::SkipNextPropertyOrAggregate()
|
|
{
|
|
UInt32 dataLen;
|
|
UInt16 propertyType;
|
|
UInt16 beginTagType;
|
|
|
|
if (PeekNextPropertyTag(&propertyType, &dataLen) != DrError_OK)
|
|
return status;
|
|
|
|
// If it's not a begin tag, just skip the property and return
|
|
if (propertyType != Prop_Dryad_BeginTag)
|
|
return SkipNextProperty();
|
|
|
|
// Read the begin tag type
|
|
if (ReadNextUInt16Property(Prop_Dryad_BeginTag, &beginTagType) != DrError_OK)
|
|
return status;
|
|
|
|
// Skip until corresponding end tag
|
|
// If another BeginTag is encountered, recurse as appropriate
|
|
while (TRUE)
|
|
{
|
|
if (PeekNextPropertyTag(&propertyType, &dataLen) != DrError_OK)
|
|
return status;
|
|
|
|
if (propertyType == Prop_Dryad_BeginTag)
|
|
{
|
|
if (SkipNextPropertyOrAggregate() != DrError_OK)
|
|
return status;
|
|
}
|
|
else if (propertyType == Prop_Dryad_EndTag)
|
|
{
|
|
UInt16 endTagType;
|
|
|
|
if (ReadNextUInt16Property(Prop_Dryad_EndTag, &endTagType) != DrError_OK)
|
|
return status;
|
|
|
|
if (endTagType != beginTagType)
|
|
return SetStatus(DrError_InvalidProperty);
|
|
|
|
return DrError_OK;
|
|
}
|
|
else
|
|
{
|
|
if (SkipNextProperty() != DrError_OK)
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DrError DrMemoryBufferWriter::SetBufferOffset(Size_t offset)
|
|
{
|
|
if (status == DrError_OK && !MemoryWriterIsClosed()) {
|
|
FlushMemoryWriter();
|
|
if (status == DrError_OK) {
|
|
Size_t uSize;
|
|
BYTE *p = (BYTE *)m_pBuffer->GetWriteAddress(offset, (Size_t)1, &uSize);
|
|
if (p == NULL) {
|
|
SetStatus(DrError_EndOfStream);
|
|
} else {
|
|
uBlockBasePhysicalStreamPosition = (UInt64)offset;
|
|
pData = pBlockBase = p;
|
|
blockLength = uSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
DrError DrMemoryBufferWriter::FlushMemoryWriter()
|
|
{
|
|
if (status == DrError_OK && !MemoryWriterIsClosed())
|
|
{
|
|
if (m_pBuffer != NULL)
|
|
{
|
|
Size_t newPos = GetBufferOffset();
|
|
if (m_fTruncateOnFlush || newPos > m_pBuffer->GetAvailableSize()) {
|
|
m_pBuffer->SetAvailableSize(newPos);
|
|
}
|
|
}
|
|
// DrMemoryWriter::FlushMemoryWriter()
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void DrMemoryBufferWriter::InternalFree()
|
|
{
|
|
m_pBuffer = NULL;
|
|
}
|
|
|
|
DrError DrMemoryBufferWriter::CloseMemoryWriter()
|
|
{
|
|
if (!MemoryWriterIsClosed())
|
|
{
|
|
DrMemoryWriter::CloseMemoryWriter(); // this will call FlushMemoryWriter
|
|
InternalFree();
|
|
}
|
|
return status;
|
|
}
|
|
|
|
DrMemoryBufferWriter::~DrMemoryBufferWriter()
|
|
{
|
|
//=======================================================================================================
|
|
// NOTE: each subclass should duplicate this code if it implements CloseMemoryWriter or FlushMemoryWriter
|
|
//
|
|
if (!MemoryWriterIsClosed())
|
|
{
|
|
// Close the memory writer. We cannot tolerate new failures in CloseMemoryWriter at this point because we are in a destructor!
|
|
// This will call FlushMemoryWriter() before closing, but because of virtual destructor unwinding, it will call the
|
|
// current class's implementation rather than the subclass's overriding implementation. So each subclass needs to implement this call in their
|
|
// virtual destructor if they implement CloseMemoryWriter or FlushMemoryWriter
|
|
MemoryWriterDestructorClose();
|
|
}
|
|
//
|
|
// NOTE: End dublicated destructor code
|
|
//=======================================================================================================
|
|
InternalFree();
|
|
}
|
|
|
|
DrError DrMemoryBufferWriter::AdvanceToNextBlock()
|
|
{
|
|
if (status == DrError_OK) {
|
|
Size_t newPos = GetBufferOffset();
|
|
m_pBuffer->SetAvailableSize(newPos);
|
|
Size_t uNewBlockSize;
|
|
BYTE *p = (BYTE *)m_pBuffer->GetWriteAddressIfPossible(newPos, (Size_t)1, &uNewBlockSize);
|
|
if (p == NULL) {
|
|
SetStatus(DrError_EndOfStream);
|
|
} else {
|
|
|
|
// Update DrMemoryWriter context for the new contiguous block
|
|
pBlockBase = p;
|
|
blockLength = uNewBlockSize;
|
|
pData = pBlockBase;
|
|
uBlockBasePhysicalStreamPosition = (UInt64)newPos;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool DrMemoryBufferWriter::FutureBlocksCanBeWritten(Size_t length)
|
|
{
|
|
(void)length;
|
|
return true;
|
|
}
|
|
|
|
void DrMemoryBufferReader::InternalFree()
|
|
{
|
|
m_pBuffer = NULL; // decrefs the buffer
|
|
nextReadOffset = 0;
|
|
}
|
|
|
|
DrError DrMemoryBufferReader::CloseMemoryReader()
|
|
{
|
|
if (!MemoryReaderIsClosed())
|
|
{
|
|
DrMemoryReader::CloseMemoryReader();
|
|
InternalFree();
|
|
}
|
|
return status;
|
|
}
|
|
|
|
DrMemoryBufferReader::~DrMemoryBufferReader()
|
|
{
|
|
InternalFree();
|
|
}
|
|
|
|
DrError DrMemoryBufferReader::SetBufferOffset(Size_t offset)
|
|
{
|
|
if (status == DrError_OK && !MemoryReaderIsClosed()) {
|
|
Size_t uStreamSize = 0;
|
|
if (m_pBuffer != NULL) {
|
|
uStreamSize = m_pBuffer->GetAvailableSize();
|
|
}
|
|
if (m_pBuffer == NULL || offset > uStreamSize) {
|
|
SetStatus(DrError_EndOfStream);
|
|
} else {
|
|
DiscardMemoryReaderContext();
|
|
const BYTE *p = NULL;
|
|
Size_t uSize = 0;
|
|
if (offset < uStreamSize) {
|
|
p = (const BYTE *)m_pBuffer->GetReadAddress(offset, &uSize);
|
|
LogAssert (p != NULL && uSize > 0);
|
|
}
|
|
uBlockBasePhysicalStreamPosition = (UInt64)offset;
|
|
nextReadOffset = offset + uSize;
|
|
pData = pBlockBase = (BYTE *)p;
|
|
blockLength = uSize;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
DrError DrMemoryBufferReader::ReadNextBlock(/* out */ const BYTE **ppBytes, /* out */ Size_t *pLength)
|
|
{
|
|
*ppBytes = NULL;
|
|
*pLength = 0;
|
|
if (status == DrError_OK) {
|
|
Size_t uSize;
|
|
if (m_pBuffer == NULL || nextReadOffset >= m_pBuffer->GetAvailableSize()) {
|
|
SetStatus(DrError_EndOfStream);
|
|
} else {
|
|
const BYTE *p = (const BYTE *)m_pBuffer->GetReadAddress(nextReadOffset, &uSize);
|
|
LogAssert (p != NULL && uSize > 0);
|
|
*ppBytes = p;
|
|
*pLength = uSize;
|
|
nextReadOffset += uSize;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
DrError DrMemoryBufferReader::FutureBlockPeekBytes(/* out */ void *pBytes, Size_t length)
|
|
{
|
|
if (status == DrError_OK) {
|
|
Size_t cbAvailable = m_pBuffer->GetAvailableSize();
|
|
if (m_pBuffer == NULL || nextReadOffset > cbAvailable || cbAvailable - nextReadOffset < length) {
|
|
SetStatus(DrError_EndOfStream);
|
|
} else {
|
|
m_pBuffer->Read(nextReadOffset, pBytes, length);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool DrMemoryBufferReader::FutureBlocksCanBeRead(Size_t length)
|
|
{
|
|
if (status != DrError_OK) {
|
|
return false;
|
|
}
|
|
Size_t cbAvailable = m_pBuffer->GetAvailableSize();
|
|
return (m_pBuffer != NULL && nextReadOffset <= cbAvailable && cbAvailable - nextReadOffset >= length);
|
|
}
|