/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */

// TODO: Read->RefreshBuffer-> React to changes from m_nBufActualLen

#include <sal/config.h>

#include <cassert>
#include <cstddef>
#include <memory>

#include <string.h>

#include <o3tl/safeint.hxx>
#include <osl/endian.h>
#include <osl/diagnose.h>
#include <rtl/strbuf.hxx>
#include <rtl/string.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <tools/long.hxx>

#include <comphelper/fileformat.h>
#include <comphelper/fileurl.hxx>

static void swapNibbles(unsigned char &c)
{
    unsigned char nSwapTmp=c;
    nSwapTmp <<= 4;
    c >>= 4;
    c |= nSwapTmp;
}

#include <tools/debug.hxx>
#include <tools/stream.hxx>
#include <osl/thread.h>
#include <algorithm>

// !!! Do not inline if already the operators <<,>> are inline
template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 2, int> = 0>
static void SwapNumber(T& r)
    {   r = OSL_SWAPWORD(r);   }
template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 4, int> = 0>
static void SwapNumber(T& r)
    {   r = OSL_SWAPDWORD(r);   }
template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 8, int> = 0>
static void SwapNumber(T& r)
    {
        union
        {
            T n;
            sal_uInt32 c[2];
        } s;

        s.n = r;
        std::swap(s.c[0], s.c[1]); // swap the 32 bit words
        // swap the bytes in the words
        s.c[0] = OSL_SWAPDWORD(s.c[0]);
        s.c[1] = OSL_SWAPDWORD(s.c[1]);
        r = s.n;
    }

#ifdef UNX
static void SwapFloat( float& r )
    {
        union
        {
            float f;
            sal_uInt32 c;
        } s;

        s.f = r;
        s.c = OSL_SWAPDWORD( s.c );
        r = s.f;
    }

static void SwapDouble( double& r )
    {
        if( sizeof(double) != 8 )
        {
          SAL_WARN( "tools.stream", "Can only swap 8-Byte-doubles" );
        }
        else
        {
            union
            {
                double d;
                sal_uInt32 c[2];
            } s;

            s.d = r;
            s.c[0] ^= s.c[1]; // swap 32-bit values in situ
            s.c[1] ^= s.c[0];
            s.c[0] ^= s.c[1];
            s.c[0] = OSL_SWAPDWORD(s.c[0]); // swap dword itself in situ
            s.c[1] = OSL_SWAPDWORD(s.c[1]);
            r = s.d;
        }
    }
#endif

//SDO

void SvStream::readNumberWithoutSwap_(void * pDataDest, int nDataSize)
{
    if (m_isIoRead && nDataSize <= m_nBufFree)
    {
        for (int i = 0; i < nDataSize; i++)
            static_cast<char*>(pDataDest)[i] = m_pBufPos[i];
        m_nBufActualPos += nDataSize;
        m_pBufPos += nDataSize;
        m_nBufFree -= nDataSize;
    }
    else
    {
        ReadBytes( pDataDest, nDataSize );
    }
}


void SvStream::writeNumberWithoutSwap_(const void * pDataSrc, int nDataSize)
{
    if (m_isIoWrite && nDataSize <= m_nBufFree)
    {
        for (int i = 0; i < nDataSize; i++)
            m_pBufPos[i] = static_cast<const char*>(pDataSrc)[i];
        m_nBufFree -= nDataSize;
        m_nBufActualPos += nDataSize;
        if (m_nBufActualPos > m_nBufActualLen)
            m_nBufActualLen = m_nBufActualPos;
        m_pBufPos += nDataSize;
        m_isDirty = true;
    }
    else
    {
        WriteBytes( pDataSrc, nDataSize );
    }
}


void SvLockBytes::close()
{
    if (m_bOwner)
        delete m_pStream;
    m_pStream = nullptr;
}


// virtual
ErrCode SvLockBytes::ReadAt(sal_uInt64 const nPos, void * pBuffer, std::size_t nCount,
                            std::size_t * pRead) const
{
    if (!m_pStream)
    {
        OSL_FAIL("SvLockBytes::ReadAt(): Bad stream");
        return ERRCODE_NONE;
    }

    m_pStream->Seek(nPos);
    std::size_t nTheRead = m_pStream->ReadBytes(pBuffer, nCount);
    if (pRead)
        *pRead = nTheRead;
    return m_pStream->GetErrorCode();
}

// virtual
ErrCode SvLockBytes::WriteAt(sal_uInt64 const nPos, const void * pBuffer, std::size_t nCount,
                             std::size_t * pWritten)
{
    if (!m_pStream)
    {
        OSL_FAIL("SvLockBytes::WriteAt(): Bad stream");
        return ERRCODE_NONE;
    }

    m_pStream->Seek(nPos);
    std::size_t nTheWritten = m_pStream->WriteBytes(pBuffer, nCount);
    if (pWritten)
        *pWritten = nTheWritten;
    return m_pStream->GetErrorCode();
}

// virtual
ErrCode SvLockBytes::Flush() const
{
    if (!m_pStream)
    {
        OSL_FAIL("SvLockBytes::Flush(): Bad stream");
        return ERRCODE_NONE;
    }

    m_pStream->Flush();
    return m_pStream->GetErrorCode();
}

// virtual
ErrCode SvLockBytes::SetSize(sal_uInt64 const nSize)
{
    if (!m_pStream)
    {
        OSL_FAIL("SvLockBytes::SetSize(): Bad stream");
        return ERRCODE_NONE;
    }

    m_pStream->SetStreamSize(nSize);
    return m_pStream->GetErrorCode();
}

ErrCode SvLockBytes::Stat(SvLockBytesStat * pStat) const
{
    if (!m_pStream)
    {
        OSL_FAIL("SvLockBytes::Stat(): Bad stream");
        return ERRCODE_NONE;
    }

    if (pStat)
        pStat->nSize = m_pStream->TellEnd();
    return ERRCODE_NONE;
}


std::size_t SvStream::GetData( void* pData, std::size_t nSize )
{
    if( !GetError() )
    {
        DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
        std::size_t nRet(0);
        m_nError = m_xLockBytes->ReadAt(m_nActPos, pData, nSize, &nRet);
        m_nActPos += nRet;
        return nRet;
    }
    else return 0;
}

std::size_t SvStream::PutData( const void* pData, std::size_t nSize )
{
    if( !GetError() )
    {
        DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
        std::size_t nRet(0);
        m_nError = m_xLockBytes->WriteAt(m_nActPos, pData, nSize, &nRet);
        m_nActPos += nRet;
        return nRet;
    }
    else return 0;
}

sal_uInt64 SvStream::SeekPos(sal_uInt64 const nPos)
{
    // check if a truncated STREAM_SEEK_TO_END was passed
    assert(nPos != SAL_MAX_UINT32);
    if( !GetError() && nPos == STREAM_SEEK_TO_END )
    {
        DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
        SvLockBytesStat aStat;
        m_xLockBytes->Stat( &aStat );
        m_nActPos = aStat.nSize;
    }
    else
        m_nActPos = nPos;
    return m_nActPos;
}

void SvStream::FlushData()
{
    if( !GetError() )
    {
        DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
        m_nError = m_xLockBytes->Flush();
    }
}

void SvStream::SetSize(sal_uInt64 const nSize)
{
    DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
    m_nError = m_xLockBytes->SetSize( nSize );
}

SvStream::SvStream() :
     m_nActPos(0)

   , m_pBufPos(nullptr)
   , m_nBufSize(0)
   , m_nBufActualLen(0)
   , m_nBufActualPos(0)
   , m_nBufFree(0)
   , m_isIoRead(false)
   , m_isIoWrite(false)

   , m_isDirty(false)
   , m_isEof(false)

   , m_nCompressMode(SvStreamCompressFlags::NONE)
#if defined UNX
   , m_eLineDelimiter(LINEEND_LF)   // UNIX-Format
#else
   , m_eLineDelimiter(LINEEND_CRLF) // DOS-Format
#endif
   , m_eStreamCharSet(osl_getThreadTextEncoding())

   , m_nCryptMask(0)

   , m_nVersion(0)

   , m_nBufFilePos(0)
   , m_eStreamMode(StreamMode::NONE)
   , m_isWritable(true)

{
    SetEndian( SvStreamEndian::LITTLE );

    ClearError();
}

SvStream::SvStream( SvLockBytes* pLockBytesP ) : SvStream()
{
    m_xLockBytes = pLockBytesP;
    if( pLockBytesP ) {
        const SvStream* pStrm = pLockBytesP->GetStream();
        if( pStrm ) {
            SetError( pStrm->GetErrorCode() );
        }
    }
    SetBufferSize( 256 );
}

SvStream::~SvStream()
{
    if (m_xLockBytes.is())
        Flush();
}

void SvStream::ClearError()
{
    m_isEof = false;
    m_nError = ERRCODE_NONE;
}

void SvStream::SetError( ErrCode nErrorCode )
{
    if (m_nError == ERRCODE_NONE)
        m_nError = nErrorCode;
}

void SvStream::SetEndian( SvStreamEndian nNewFormat )
{
#ifdef OSL_BIGENDIAN
    m_isSwap = nNewFormat == SvStreamEndian::LITTLE;
#else
    m_isSwap = nNewFormat == SvStreamEndian::BIG;
#endif
}

SvStreamEndian SvStream::GetEndian() const
{
#ifdef OSL_BIGENDIAN
    return m_isSwap ? SvStreamEndian::LITTLE : SvStreamEndian::BIG;
#else
    return m_isSwap ? SvStreamEndian::BIG : SvStreamEndian::LITTLE;
#endif
}

void SvStream::SetBufferSize( sal_uInt16 nBufferSize )
{
    sal_uInt64 const nActualFilePos = Tell();
    bool bDontSeek = (m_pRWBuf == nullptr);

    if (m_isDirty && m_isWritable)  // due to Windows NT: Access denied
        FlushBuffer();

    if (m_nBufSize)
    {
        m_pRWBuf.reset();
        m_nBufFilePos += m_nBufActualPos;
    }

    m_pRWBuf        = nullptr;
    m_nBufActualLen = 0;
    m_nBufActualPos = 0;
    m_nBufSize      = nBufferSize;
    if (m_nBufSize)
        m_pRWBuf.reset(new sal_uInt8[ m_nBufSize ]);
    m_pBufPos       = m_pRWBuf.get();
    m_isIoRead = m_isIoWrite = false;
    if( !bDontSeek )
        SeekPos( nActualFilePos );
}

void SvStream::ClearBuffer()
{
    m_nBufActualLen = 0;
    m_nBufActualPos = 0;
    m_nBufFilePos   = 0;
    m_pBufPos       = m_pRWBuf.get();
    m_isDirty       = false;
    m_isIoRead = m_isIoWrite = false;

    m_isEof         = false;
}

void SvStream::ResetError()
{
    ClearError();
}

bool SvStream::ReadByteStringLine( OUString& rStr, rtl_TextEncoding eSrcCharSet,
                                       sal_Int32 nMaxBytesToRead )
{
    OString aStr;
    bool bRet = ReadLine( aStr, nMaxBytesToRead);
    rStr = OStringToOUString(aStr, eSrcCharSet);
    return bRet;
}

bool SvStream::ReadLine( OString& rStr, sal_Int32 nMaxBytesToRead )
{
    OStringBuffer aBuf(4096);
    bool rv = ReadLine(aBuf, nMaxBytesToRead);
    rStr = aBuf.makeStringAndClear();
    return rv;
}

bool SvStream::ReadLine( OStringBuffer& aBuf, sal_Int32 nMaxBytesToRead )
{
    char    buf[256+1];
    bool        bEnd        = false;
    sal_uInt64  nOldFilePos = Tell();
    char    c           = 0;
    std::size_t nTotalLen   = 0;

    aBuf.setLength(0);
    while( !bEnd && !GetError() )   // Don't test for EOF as we
                                    // are reading block-wise!
    {
        sal_uInt16 nLen = static_cast<sal_uInt16>(ReadBytes(buf, sizeof(buf)-1));
        if ( !nLen )
        {
            if ( aBuf.isEmpty() )
            {
                // Exit on first block-read error
                m_isEof = true;
                aBuf.setLength(0);
                return false;
            }
            else
                break;
        }

        sal_uInt16 j, n;
        for( j = n = 0; j < nLen ; ++j )
        {
            c = buf[j];
            if ( c == '\n' || c == '\r' )
            {
                bEnd = true;
                break;
            }
            if ( n < j )
                buf[n] = c;
            ++n;
        }
        nTotalLen += j;
        if (nTotalLen > o3tl::make_unsigned(nMaxBytesToRead))
        {
            n -= nTotalLen - nMaxBytesToRead;
            nTotalLen = nMaxBytesToRead;
            bEnd = true;
        }
        if ( n )
            aBuf.append(buf, n);
    }

    if ( !bEnd && !GetError() && !aBuf.isEmpty() )
        bEnd = true;

    nOldFilePos += nTotalLen;
    if( Tell() > nOldFilePos )
        nOldFilePos++;
    Seek( nOldFilePos );  // Seek pointer due to BlockRead above

    if ( bEnd && (c=='\r' || c=='\n') )  // Special treatment for DOS files
    {
        char cTemp;
        std::size_t nLen = ReadBytes(&cTemp, sizeof(cTemp));
        if ( nLen ) {
            if( cTemp == c || (cTemp != '\n' && cTemp != '\r') )
                Seek( nOldFilePos );
        }
    }

    if ( bEnd )
        m_isEof = false;
    return bEnd;
}

bool SvStream::ReadUniStringLine( OUString& rStr, sal_Int32 nMaxCodepointsToRead )
{
    sal_Unicode buf[256+1];
    bool        bEnd        = false;
    sal_uInt64  nOldFilePos = Tell();
    sal_Unicode c           = 0;
    std::size_t nTotalLen   = 0;

    DBG_ASSERT( sizeof(sal_Unicode) == sizeof(sal_uInt16), "ReadUniStringLine: swapping sizeof(sal_Unicode) not implemented" );

    OUStringBuffer aBuf(4096);
    while( !bEnd && !GetError() )   // Don't test for EOF as we
                                    // are reading block-wise!
    {
        sal_uInt16 nLen = static_cast<sal_uInt16>(ReadBytes( buf, sizeof(buf)-sizeof(sal_Unicode)));
        nLen /= sizeof(sal_Unicode);
        if ( !nLen )
        {
            if ( aBuf.isEmpty() )
            {
                // exit on first BlockRead error
                m_isEof = true;
                rStr.clear();
                return false;
            }
            else
                break;
        }

        sal_uInt16 j, n;
        for( j = n = 0; j < nLen ; ++j )
        {
            if (m_isSwap)
                SwapNumber( buf[n] );
            c = buf[j];
            if ( c == '\n' || c == '\r' )
            {
                bEnd = true;
                break;
            }
            // erAck 26.02.01: Old behavior was no special treatment of '\0'
            // character here, but a following rStr+=c did ignore it. Is this
            // really intended? Or should a '\0' better terminate a line?
            // The nOldFilePos stuff wasn't correct then anyways.
            if ( c )
            {
                if ( n < j )
                    buf[n] = c;
                ++n;
            }
        }
        nTotalLen += j;
        if (nTotalLen > o3tl::make_unsigned(nMaxCodepointsToRead))
        {
            n -= nTotalLen - nMaxCodepointsToRead;
            nTotalLen = nMaxCodepointsToRead;
            bEnd = true;
        }
        if ( n )
            aBuf.append( buf, n );
    }

    if ( !bEnd && !GetError() && !aBuf.isEmpty() )
        bEnd = true;

    nOldFilePos += nTotalLen * sizeof(sal_Unicode);
    if( Tell() > nOldFilePos )
        nOldFilePos += sizeof(sal_Unicode);
    Seek( nOldFilePos );  // seek due to BlockRead above

    if ( bEnd && (c=='\r' || c=='\n') )  // special treatment for DOS files
    {
        sal_Unicode cTemp;
        ReadBytes( &cTemp, sizeof(cTemp) );
        if (m_isSwap)
            SwapNumber( cTemp );
        if( cTemp == c || (cTemp != '\n' && cTemp != '\r') )
            Seek( nOldFilePos );
    }

    if ( bEnd )
        m_isEof = false;
    rStr = aBuf.makeStringAndClear();
    return bEnd;
}

bool SvStream::ReadUniOrByteStringLine( OUString& rStr, rtl_TextEncoding eSrcCharSet,
                                            sal_Int32 nMaxCodepointsToRead )
{
    if ( eSrcCharSet == RTL_TEXTENCODING_UNICODE )
        return ReadUniStringLine( rStr, nMaxCodepointsToRead );
    else
        return ReadByteStringLine( rStr, eSrcCharSet, nMaxCodepointsToRead );
}

OString read_zeroTerminated_uInt8s_ToOString(SvStream& rStream)
{
    OStringBuffer aOutput(256);

    char buf[ 256 + 1 ];
    bool bEnd = false;
    sal_uInt64 nFilePos = rStream.Tell();

    while( !bEnd && !rStream.GetError() )
    {
        std::size_t nLen = rStream.ReadBytes(buf, sizeof(buf)-1);
        if (!nLen)
            break;

        std::size_t nReallyRead = nLen;
        const char* pPtr = buf;
        while (nLen && *pPtr)
        {
            ++pPtr;
            --nLen;
        }

        bEnd =  ( nReallyRead < sizeof(buf)-1 )         // read less than attempted to read
                ||  (  ( nLen > 0 )                    // OR it is inside the block we read
                    &&  ( 0 == *pPtr )                  //    AND found a string terminator
                    );

        aOutput.append(buf, pPtr - buf);
    }

    nFilePos += aOutput.getLength();
    if (rStream.Tell() > nFilePos)
        rStream.Seek(nFilePos+1);  // seek due to FileRead above
    return aOutput.makeStringAndClear();
}

OUString read_zeroTerminated_uInt8s_ToOUString(SvStream& rStream, rtl_TextEncoding eEnc)
{
    return OStringToOUString(
        read_zeroTerminated_uInt8s_ToOString(rStream), eEnc);
}

/** Attempt to write a prefixed sequence of nUnits 16bit units from an OUString,
    returned value is number of bytes written */
std::size_t write_uInt16s_FromOUString(SvStream& rStrm, std::u16string_view rStr,
    std::size_t nUnits)
{
    DBG_ASSERT( sizeof(sal_Unicode) == sizeof(sal_uInt16), "write_uInt16s_FromOUString: swapping sizeof(sal_Unicode) not implemented" );
    std::size_t nWritten;
    if (!rStrm.IsEndianSwap())
        nWritten = rStrm.WriteBytes(rStr.data(), nUnits * sizeof(sal_Unicode));
    else
    {
        std::size_t nLen = nUnits;
        sal_Unicode aBuf[384];
        sal_Unicode* const pTmp = ( nLen > 384 ? new sal_Unicode[nLen] : aBuf);
        memcpy( pTmp, rStr.data(), nLen * sizeof(sal_Unicode) );
        sal_Unicode* p = pTmp;
        const sal_Unicode* const pStop = pTmp + nLen;
        while ( p < pStop )
        {
            SwapNumber( *p );
            p++;
        }
        nWritten = rStrm.WriteBytes( pTmp, nLen * sizeof(sal_Unicode) );
        if ( pTmp != aBuf )
            delete [] pTmp;
    }
    return nWritten;
}

bool SvStream::WriteUnicodeOrByteText( std::u16string_view rStr, rtl_TextEncoding eDestCharSet )
{
    if ( eDestCharSet == RTL_TEXTENCODING_UNICODE )
    {
        write_uInt16s_FromOUString(*this, rStr, rStr.size());
        return m_nError == ERRCODE_NONE;
    }
    else
    {
        OString aStr(OUStringToOString(rStr, eDestCharSet));
        write_uInt8s_FromOString(*this, aStr, aStr.getLength());
        return m_nError == ERRCODE_NONE;
    }
}

bool SvStream::WriteByteStringLine( std::u16string_view rStr, rtl_TextEncoding eDestCharSet )
{
    return WriteLine(OUStringToOString(rStr, eDestCharSet));
}

bool SvStream::WriteLine(std::string_view rStr)
{
    WriteBytes(rStr.data(), rStr.size());
    endl(*this);
    return m_nError == ERRCODE_NONE;
}

bool SvStream::WriteUniOrByteChar( sal_Unicode ch, rtl_TextEncoding eDestCharSet )
{
    if ( eDestCharSet == RTL_TEXTENCODING_UNICODE )
        WriteUnicode(ch);
    else
    {
        OString aStr(&ch, 1, eDestCharSet);
        WriteBytes(aStr.getStr(), aStr.getLength());
    }
    return m_nError == ERRCODE_NONE;
}

void SvStream::StartWritingUnicodeText()
{
    m_isSwap = false; // Switch to no endian swapping
    // BOM, Byte Order Mark, U+FEFF, see
    // http://www.unicode.org/faq/utf_bom.html#BOM
    // Upon read: 0xfeff(-257) => no swap; 0xfffe(-2) => swap
    WriteUInt16(0xfeff);
}

void SvStream::StartReadingUnicodeText( rtl_TextEncoding eReadBomCharSet )
{
    if (!(  eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
            eReadBomCharSet == RTL_TEXTENCODING_UNICODE ||
            eReadBomCharSet == RTL_TEXTENCODING_UTF8))
        return;    // nothing to read

    const sal_uInt64 nOldPos = Tell();
    bool bGetBack = true;
    unsigned char nFlag(0);
    ReadUChar( nFlag );
    switch ( nFlag )
    {
        case 0xfe: // UTF-16BE?
            if (    eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
                    eReadBomCharSet == RTL_TEXTENCODING_UNICODE)
            {
                ReadUChar(nFlag);
                if (nFlag == 0xff)
                {
                    SetEndian(SvStreamEndian::BIG);
                    bGetBack = false;
                }
            }
        break;
        case 0xff: // UTF-16LE?
            if (    eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
                    eReadBomCharSet == RTL_TEXTENCODING_UNICODE)
            {
                ReadUChar(nFlag);
                if (nFlag == 0xfe)
                {
                    SetEndian(SvStreamEndian::LITTLE);
                    bGetBack = false;
                }
            }
        break;
        case 0xef: // UTF-8?
            if (    eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
                    eReadBomCharSet == RTL_TEXTENCODING_UTF8)
            {
                ReadUChar(nFlag);
                if (nFlag == 0xbb)
                {
                    ReadUChar(nFlag);
                    if (nFlag == 0xbf)
                        bGetBack = false; // it is UTF-8
                }
            }
        break;
        default:
            ;   // nothing
    }
    if (bGetBack)
        Seek(nOldPos);      // no BOM, pure data
}

sal_uInt64 SvStream::SeekRel(sal_Int64 const nPos)
{
    sal_uInt64 nActualPos = Tell();

    if ( nPos >= 0 )
    {
        if (SAL_MAX_UINT64 - nActualPos > o3tl::make_unsigned(nPos))
            nActualPos += nPos;
    }
    else
    {
        sal_uInt64 const nAbsPos = static_cast<sal_uInt64>(-nPos);
        if ( nActualPos >= nAbsPos )
            nActualPos -= nAbsPos;
    }

    assert((m_pBufPos != nullptr) == bool(m_pRWBuf));
    if (m_pRWBuf)
    {
        m_pBufPos = m_pRWBuf.get() + nActualPos;
    }
    return Seek( nActualPos );
}

template <typename T> SvStream& SvStream::ReadNumber(T& r)
{
    T n = 0;
    readNumberWithoutSwap(n);
    if (good())
    {
        if (m_isSwap)
            SwapNumber(n);
        r = n;
    }
    return *this;
}

SvStream& SvStream::ReadUInt16(sal_uInt16& r) { return ReadNumber(r); }
SvStream& SvStream::ReadUInt32(sal_uInt32& r) { return ReadNumber(r); }
SvStream& SvStream::ReadUInt64(sal_uInt64& r) { return ReadNumber(r); }
SvStream& SvStream::ReadInt16(sal_Int16& r) { return ReadNumber(r); }
SvStream& SvStream::ReadInt32(sal_Int32& r) { return ReadNumber(r); }
SvStream& SvStream::ReadInt64(sal_Int64& r) { return ReadNumber(r); }

SvStream& SvStream::ReadSChar( signed char& r )
{
    if (m_isIoRead && sizeof(signed char) <= m_nBufFree)
    {
        r = *m_pBufPos;
        m_nBufActualPos += sizeof(signed char);
        m_pBufPos += sizeof(signed char);
        m_nBufFree -= sizeof(signed char);
    }
    else
        ReadBytes( &r, sizeof(signed char) );
    return *this;
}

// Special treatment for Chars due to PutBack

SvStream& SvStream::ReadChar( char& r )
{
    if (m_isIoRead && sizeof(char) <= m_nBufFree)
    {
        r = *m_pBufPos;
        m_nBufActualPos += sizeof(char);
        m_pBufPos += sizeof(char);
        m_nBufFree -= sizeof(char);
    }
    else
        ReadBytes( &r, sizeof(char) );
    return *this;
}

SvStream& SvStream::ReadUChar( unsigned char& r )
{
    if (m_isIoRead && sizeof(char) <= m_nBufFree)
    {
        r = *m_pBufPos;
        m_nBufActualPos += sizeof(char);
        m_pBufPos += sizeof(char);
        m_nBufFree -= sizeof(char);
    }
    else
        ReadBytes( &r, sizeof(char) );
    return *this;
}

SvStream& SvStream::ReadUtf16(sal_Unicode& r) { return ReadNumber(r); }

SvStream& SvStream::ReadCharAsBool( bool& r )
{
    if (m_isIoRead && sizeof(char) <= m_nBufFree)
    {
        SAL_WARN_IF(
            *m_pBufPos > 1, "tools.stream", unsigned(*m_pBufPos) << " not 0/1");
        r = *m_pBufPos != 0;
        m_nBufActualPos += sizeof(char);
        m_pBufPos += sizeof(char);
        m_nBufFree -= sizeof(char);
    }
    else
    {
        unsigned char c;
        if (ReadBytes(&c, 1) == 1)
        {
            SAL_WARN_IF(c > 1, "tools.stream", unsigned(c) << " not 0/1");
            r = c != 0;
        }
    }
    return *this;
}

SvStream& SvStream::ReadFloat(float& r)
{
    float n = 0;
    readNumberWithoutSwap(n);
    if (good())
    {
#if defined UNX
        if (m_isSwap)
          SwapFloat(n);
#endif
        r = n;
    }
    return *this;
}

SvStream& SvStream::ReadDouble(double& r)
{
    double n = 0;
    readNumberWithoutSwap(n);
    if (good())
    {
#if defined UNX
        if (m_isSwap)
          SwapDouble(n);
#endif
        r = n;
    }
    return *this;
}

SvStream& SvStream::ReadStream( SvStream& rStream )
{
    const sal_uInt32 cBufLen = 0x8000;
    std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );

    sal_uInt32 nCount;
    do {
        nCount = ReadBytes( pBuf.get(), cBufLen );
        rStream.WriteBytes( pBuf.get(), nCount );
    } while( nCount == cBufLen );

    return *this;
}

template <typename T> SvStream& SvStream::WriteNumber(T n)
{
    if (m_isSwap)
        SwapNumber(n);
    writeNumberWithoutSwap(n);
    return *this;
}

SvStream& SvStream::WriteUInt16(sal_uInt16 v) { return WriteNumber(v); }
SvStream& SvStream::WriteUInt32(sal_uInt32 v) { return WriteNumber(v); }
SvStream& SvStream::WriteUInt64(sal_uInt64 v) { return WriteNumber(v); }
SvStream& SvStream::WriteInt16(sal_Int16 v) { return WriteNumber(v); }
SvStream& SvStream::WriteInt32(sal_Int32 v) { return WriteNumber(v); }
SvStream& SvStream::WriteInt64(sal_Int64 v) { return WriteNumber(v); }

SvStream& SvStream::WriteSChar( signed char v )
{
    //SDO
    if (m_isIoWrite && sizeof(signed char) <= m_nBufFree)
    {
        *m_pBufPos = v;
        m_pBufPos++; // sizeof(char);
        m_nBufActualPos++;
        if (m_nBufActualPos > m_nBufActualLen)  // Append ?
            m_nBufActualLen = m_nBufActualPos;
        m_nBufFree--; // = sizeof(char);
        m_isDirty = true;
    }
    else
        WriteBytes( &v, sizeof(signed char) );
    return *this;
}

// Special treatment for Chars due to PutBack

SvStream& SvStream::WriteChar( char v )
{
    //SDO
    if (m_isIoWrite && sizeof(char) <= m_nBufFree)
    {
        *m_pBufPos = v;
        m_pBufPos++; // sizeof(char);
        m_nBufActualPos++;
        if (m_nBufActualPos > m_nBufActualLen)  // Append ?
            m_nBufActualLen = m_nBufActualPos;
        m_nBufFree--; // = sizeof(char);
        m_isDirty = true;
    }
    else
        WriteBytes( &v, sizeof(char) );
    return *this;
}

SvStream& SvStream::WriteUChar( unsigned char v )
{
//SDO
    if (m_isIoWrite && sizeof(char) <= m_nBufFree)
    {
        *reinterpret_cast<unsigned char*>(m_pBufPos) = v;
        m_pBufPos++; // = sizeof(char);
        m_nBufActualPos++; // = sizeof(char);
        if (m_nBufActualPos > m_nBufActualLen)  // Append ?
            m_nBufActualLen = m_nBufActualPos;
        m_nBufFree--;
        m_isDirty = true;
    }
    else
        WriteBytes( &v, sizeof(char) );
    return *this;
}

SvStream& SvStream::WriteUInt8( sal_uInt8 v )
{
    return WriteUChar(v);
}

SvStream& SvStream::WriteUnicode( sal_Unicode v )
{
    return WriteUInt16(v);
}

SvStream& SvStream::WriteFloat( float v )
{
#ifdef UNX
    if (m_isSwap)
      SwapFloat(v);
#endif
    writeNumberWithoutSwap(v);
    return *this;
}

SvStream& SvStream::WriteDouble ( const double& r )
{
#if defined UNX
    if (m_isSwap)
    {
      double nHelp = r;
      SwapDouble(nHelp);
      writeNumberWithoutSwap(nHelp);
      return *this;
    }
    else
#endif
    {
        writeNumberWithoutSwap(r);
    }
    return *this;
}

SvStream& SvStream::WriteStream( SvStream& rStream )
{
    const sal_uInt32 cBufLen = 0x8000;
    std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );
    sal_uInt32 nCount;
    do {
        nCount = rStream.ReadBytes( pBuf.get(), cBufLen );
        WriteBytes( pBuf.get(), nCount );
    } while( nCount == cBufLen );

    return *this;
}

sal_uInt64 SvStream::WriteStream( SvStream& rStream, sal_uInt64 nSize )
{
    const sal_uInt32 cBufLen = 0x8000;
    std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );
    sal_uInt32 nCurBufLen = cBufLen;
    sal_uInt32 nCount;
    sal_uInt64 nWriteSize = nSize;

    do
    {
        nCurBufLen = std::min<sal_uInt64>(nCurBufLen, nWriteSize);
        nCount = rStream.ReadBytes(pBuf.get(), nCurBufLen);
        WriteBytes( pBuf.get(), nCount );
        nWriteSize -= nCount;
    }
    while( nWriteSize && nCount == nCurBufLen );

    return nSize - nWriteSize;
}

OUString SvStream::ReadUniOrByteString( rtl_TextEncoding eSrcCharSet )
{
    // read UTF-16 string directly from stream ?
    if (eSrcCharSet == RTL_TEXTENCODING_UNICODE)
        return read_uInt32_lenPrefixed_uInt16s_ToOUString(*this);
    return read_uInt16_lenPrefixed_uInt8s_ToOUString(*this, eSrcCharSet);
}

SvStream& SvStream::WriteUniOrByteString( std::u16string_view rStr, rtl_TextEncoding eDestCharSet )
{
    // write UTF-16 string directly into stream ?
    if (eDestCharSet == RTL_TEXTENCODING_UNICODE)
        write_uInt32_lenPrefixed_uInt16s_FromOUString(*this, rStr);
    else
        write_uInt16_lenPrefixed_uInt8s_FromOUString(*this, rStr, eDestCharSet);
    return *this;
}

void SvStream::FlushBuffer()
{
    if (m_isDirty) // Does stream require a flush?
    {
        SeekPos(m_nBufFilePos);
        if (m_nCryptMask)
            CryptAndWriteBuffer(m_pRWBuf.get(), m_nBufActualLen);
        else if (PutData(m_pRWBuf.get(), m_nBufActualLen) != m_nBufActualLen)
            SetError(SVSTREAM_WRITE_ERROR);
        m_isDirty = false;
    }
}

std::size_t SvStream::ReadBytes( void* pData, std::size_t nCount )
{
    std::size_t nSaveCount = nCount;

    if (!m_pRWBuf)
    {
        nCount = GetData( pData,nCount);
        if (m_nCryptMask)
            EncryptBuffer(pData, nCount);
        m_nBufFilePos += nCount;
    }
    else
    {
        // check if block is completely within buffer
        m_isIoRead = true;
        m_isIoWrite = false;
        if (nCount <= o3tl::make_unsigned(m_nBufActualLen - m_nBufActualPos))
        {
            // => yes
            if (nCount != 0)
                memcpy(pData, m_pBufPos, nCount);
            m_nBufActualPos = m_nBufActualPos + static_cast<sal_uInt16>(nCount);
            m_pBufPos += nCount;
            m_nBufFree = m_nBufFree - static_cast<sal_uInt16>(nCount);
        }
        else
        {
            FlushBuffer();

            // Does data block fit into buffer?
            if (nCount > m_nBufSize)
            {
                // => No! Thus read directly
                // into target area without using the buffer

                m_isIoRead = false;

                SeekPos(m_nBufFilePos + m_nBufActualPos);
                m_nBufActualLen = 0;
                m_pBufPos     = m_pRWBuf.get();
                nCount = GetData( pData, nCount );
                if (m_nCryptMask)
                    EncryptBuffer(pData, nCount);
                m_nBufFilePos += nCount;
                m_nBufFilePos += m_nBufActualPos;
                m_nBufActualPos = 0;
            }
            else
            {
                // => Yes. Fill buffer first, then copy to target area

                m_nBufFilePos += m_nBufActualPos;
                SeekPos(m_nBufFilePos);

                // TODO: Typecast before GetData, sal_uInt16 nCountTmp
                std::size_t nCountTmp = GetData( m_pRWBuf.get(), m_nBufSize );
                if (m_nCryptMask)
                    EncryptBuffer(m_pRWBuf.get(), nCountTmp);
                m_nBufActualLen = static_cast<sal_uInt16>(nCountTmp);
                if( nCount > nCountTmp )
                {
                    nCount = nCountTmp;  // trim count back, EOF see below
                }
                memcpy( pData, m_pRWBuf.get(), nCount );
                m_nBufActualPos = static_cast<sal_uInt16>(nCount);
                m_pBufPos = m_pRWBuf.get() + nCount;
            }
        }
    }
    m_isEof = false;
    m_nBufFree = m_nBufActualLen - m_nBufActualPos;
    if (nCount != nSaveCount && m_nError != ERRCODE_IO_PENDING)
        m_isEof = true;
    if (nCount == nSaveCount && m_nError == ERRCODE_IO_PENDING)
        m_nError = ERRCODE_NONE;
    return nCount;
}

std::size_t SvStream::WriteBytes( const void* pData, std::size_t nCount )
{
    if( !nCount )
        return 0;

    if (!m_isWritable)
    {
        SetError( ERRCODE_IO_CANTWRITE );
        return 0;
    }

    if (!m_pRWBuf)
    {
        if (m_nCryptMask)
            nCount = CryptAndWriteBuffer( pData, nCount );
        else
            nCount = PutData( pData, nCount );
        m_nBufFilePos += nCount;
        return nCount;
    }

    m_isIoRead = false;
    m_isIoWrite = true;
    if (nCount <= o3tl::make_unsigned(m_nBufSize - m_nBufActualPos))
    {
        memcpy( m_pBufPos, pData, nCount );
        m_nBufActualPos = m_nBufActualPos + static_cast<sal_uInt16>(nCount);
        // Update length if buffer was updated
        if (m_nBufActualPos > m_nBufActualLen)
            m_nBufActualLen = m_nBufActualPos;

        m_pBufPos += nCount;
        m_isDirty = true;
    }
    else
    {
        FlushBuffer();

        // Does data block fit into buffer?
        if (nCount > m_nBufSize)
        {
            m_isIoWrite = false;
            m_nBufFilePos += m_nBufActualPos;
            m_nBufActualLen = 0;
            m_nBufActualPos = 0;
            m_pBufPos     = m_pRWBuf.get();
            SeekPos(m_nBufFilePos);
            if (m_nCryptMask)
                nCount = CryptAndWriteBuffer( pData, nCount );
            else
                nCount = PutData( pData, nCount );
            m_nBufFilePos += nCount;
        }
        else
        {
            // Copy block to buffer
            memcpy( m_pRWBuf.get(), pData, nCount );

            // Mind the order!
            m_nBufFilePos += m_nBufActualPos;
            m_nBufActualPos = static_cast<sal_uInt16>(nCount);
            m_pBufPos = m_pRWBuf.get() + nCount;
            m_nBufActualLen = static_cast<sal_uInt16>(nCount);
            m_isDirty = true;
        }
    }
    m_nBufFree = m_nBufSize - m_nBufActualPos;
    return nCount;
}

sal_uInt64 SvStream::Seek(sal_uInt64 const nFilePos)
{
    m_isIoRead = m_isIoWrite = false;
    m_isEof = false;
    if (!m_pRWBuf)
    {
        m_nBufFilePos = SeekPos( nFilePos );
        DBG_ASSERT(Tell() == m_nBufFilePos,"Out Of Sync!");
        return m_nBufFilePos;
    }

    // Is seek position within buffer?
    if (nFilePos >= m_nBufFilePos && nFilePos <= (m_nBufFilePos + m_nBufActualLen))
    {
        m_nBufActualPos = static_cast<sal_uInt16>(nFilePos - m_nBufFilePos);
        m_pBufPos = m_pRWBuf.get() + m_nBufActualPos;
        // Update m_nBufFree to avoid crash upon PutBack
        m_nBufFree = m_nBufActualLen - m_nBufActualPos;
    }
    else
    {
        FlushBuffer();
        m_nBufActualLen = 0;
        m_nBufActualPos = 0;
        m_pBufPos     = m_pRWBuf.get();
        m_nBufFilePos = SeekPos( nFilePos );
    }
    return m_nBufFilePos + m_nBufActualPos;
}

bool checkSeek(SvStream &rSt, sal_uInt64 nOffset)
{
    const sal_uInt64 nMaxSeek = rSt.TellEnd();
    return (nOffset <= nMaxSeek && rSt.Seek(nOffset) == nOffset);
}

namespace tools
{
bool isEmptyFileUrl(const OUString& rUrl)
{
    if (!comphelper::isFileUrl(rUrl))
    {
        return false;
    }

    SvFileStream aStream(rUrl, StreamMode::READ);
    if (!aStream.IsOpen())
    {
        return false;
    }

    return aStream.remainingSize() == 0;
}
}

//STREAM_SEEK_TO_END in some of the Seek backends is special cased to be
//efficient, in others e.g. SotStorageStream it's really horribly slow, and in
//those this should be overridden
sal_uInt64 SvStream::remainingSize()
{
    sal_uInt64 const nCurr = Tell();
    sal_uInt64 const nEnd = TellEnd();
    sal_uInt64 nMaxAvailable = nEnd > nCurr ? (nEnd-nCurr) : 0;
    return nMaxAvailable;
}

sal_uInt64 SvStream::TellEnd()
{
    FlushBuffer();
    sal_uInt64 const nCurr = Tell();
    sal_uInt64 const nEnd = Seek(STREAM_SEEK_TO_END);
    Seek(nCurr);
    return nEnd;
}

void SvStream::Flush()
{
    FlushBuffer();
    if (m_isWritable)
        FlushData();
}

void SvStream::RefreshBuffer()
{
    FlushBuffer();
    SeekPos(m_nBufFilePos);
    m_nBufActualLen = static_cast<sal_uInt16>(GetData( m_pRWBuf.get(), m_nBufSize ));
    if (m_nBufActualLen && m_nError == ERRCODE_IO_PENDING)
        m_nError = ERRCODE_NONE;
    if (m_nCryptMask)
        EncryptBuffer(m_pRWBuf.get(), static_cast<std::size_t>(m_nBufActualLen));
    m_isIoRead = m_isIoWrite = false;
}

#define CRYPT_BUFSIZE 1024

/// Encrypt and write
std::size_t SvStream::CryptAndWriteBuffer( const void* pStart, std::size_t nLen)
{
    unsigned char  pTemp[CRYPT_BUFSIZE];
    unsigned char const * pDataPtr = static_cast<unsigned char const *>(pStart);
    std::size_t nCount = 0;
    std::size_t nBufCount;
    unsigned char nMask = m_nCryptMask;
    do
    {
        if( nLen >= CRYPT_BUFSIZE )
            nBufCount = CRYPT_BUFSIZE;
        else
            nBufCount = nLen;
        nLen -= nBufCount;
        memcpy( pTemp, pDataPtr, static_cast<sal_uInt16>(nBufCount) );
        // ******** Encrypt ********
        for (unsigned char & rn : pTemp)
        {
            unsigned char aCh = rn;
            aCh ^= nMask;
            swapNibbles(aCh);
            rn = aCh;
        }
        // *************************
        nCount += PutData( pTemp, nBufCount );
        pDataPtr += nBufCount;
    }
    while ( nLen );
    return nCount;
}

void SvStream::EncryptBuffer(void* pStart, std::size_t nLen) const
{
    unsigned char* pTemp = static_cast<unsigned char*>(pStart);
    unsigned char nMask = m_nCryptMask;

    for ( std::size_t n=0; n < nLen; n++, pTemp++ )
    {
        unsigned char aCh = *pTemp;
        swapNibbles(aCh);
        aCh ^= nMask;
        *pTemp = aCh;
    }
}

static unsigned char implGetCryptMask(const char* pStr, sal_Int32 nLen, tools::Long nVersion)
{
    unsigned char nCryptMask = 0;

    if (!nLen)
        return nCryptMask;

    if( nVersion <= SOFFICE_FILEFORMAT_31 )
    {
        while( nLen )
        {
            nCryptMask ^= *pStr;
            pStr++;
            nLen--;
        }
    }
    else // BugFix #25888#
    {
        for( sal_Int32 i = 0; i < nLen; i++ ) {
            nCryptMask ^= pStr[i];
            if( nCryptMask & 0x80 ) {
                nCryptMask <<= 1;
                nCryptMask++;
            }
            else
                nCryptMask <<= 1;
        }
    }

    if( !nCryptMask )
        nCryptMask = 67;

    return nCryptMask;
}

void SvStream::SetCryptMaskKey(const OString& rCryptMaskKey)
{
    m_aCryptMaskKey = rCryptMaskKey;
    m_nCryptMask = implGetCryptMask(m_aCryptMaskKey.getStr(),
        m_aCryptMaskKey.getLength(), GetVersion());
}

bool SvStream::SetStreamSize(sal_uInt64 const nSize)
{
#ifdef DBG_UTIL
    sal_uInt64 nFPos = Tell();
#endif
    sal_uInt16 nBuf = m_nBufSize;
    SetBufferSize( 0 );
    SetSize( nSize );
    if (nSize < m_nBufFilePos)
    {
        m_nBufFilePos = nSize;
    }
    SetBufferSize( nBuf );
#ifdef DBG_UTIL
    DBG_ASSERT(Tell()==nFPos,"SetStreamSize failed");
#endif
    return (m_nError == ERRCODE_NONE);
}

SvStream& endl( SvStream& rStr )
{
    LineEnd eDelim = rStr.GetLineDelimiter();
    if ( eDelim == LINEEND_CR )
        rStr.WriteChar('\r');
    else if( eDelim == LINEEND_LF )
        rStr.WriteChar('\n');
    else
        rStr.WriteChar('\r').WriteChar('\n');
    return rStr;
}

SvStream& endlu( SvStream& rStrm )
{
    switch ( rStrm.GetLineDelimiter() )
    {
        case LINEEND_CR :
            rStrm.WriteUnicode('\r');
        break;
        case LINEEND_LF :
            rStrm.WriteUnicode('\n');
        break;
        default:
            rStrm.WriteUnicode('\r').WriteUnicode('\n');
    }
    return rStrm;
}

SvStream& endlub( SvStream& rStrm )
{
    if ( rStrm.GetStreamCharSet() == RTL_TEXTENCODING_UNICODE )
        return endlu( rStrm );
    else
        return endl( rStrm );
}

SvMemoryStream::SvMemoryStream( void* pBuffer, std::size_t bufSize,
                                StreamMode eMode )
{
    if( eMode & StreamMode::WRITE )
        m_isWritable = true;
    else
        m_isWritable = false;
    nEndOfData  = bufSize;
    bOwnsData   = false;
    pBuf        = static_cast<sal_uInt8 *>(pBuffer);
    nResize     = 0;
    nSize       = bufSize;
    nPos        = 0;
    SetBufferSize( 0 );
}

SvMemoryStream::SvMemoryStream( std::size_t nInitSize, std::size_t nResizeOffset )
{
    m_isWritable = true;
    bOwnsData   = true;
    nEndOfData  = 0;
    nResize     = nResizeOffset;
    nPos        = 0;
    pBuf        = nullptr;
    if( nResize != 0 && nResize < 16 )
        nResize = 16;
    if( nInitSize )
        AllocateMemory( nInitSize );
    nSize = nInitSize;
    SetBufferSize( 64 );
}

SvMemoryStream::~SvMemoryStream()
{
    if( pBuf )
    {
        if( bOwnsData )
            FreeMemory();
        else
            FlushBuffer();
    }
}

void SvMemoryStream::SetBuffer( void* pNewBuf, std::size_t nCount,
                                 std::size_t nEOF )
{
    SetBufferSize( 0 ); // Init buffering in the base class
    Seek( 0 );
    if( bOwnsData && pNewBuf != pBuf )
        FreeMemory();

    pBuf        = static_cast<sal_uInt8 *>(pNewBuf);
    nPos        = 0;
    nSize       = nCount;
    nResize     = 0;
    bOwnsData   = false;

    if( nEOF > nCount )
        nEOF = nCount;
    nEndOfData = nEOF;

    ResetError();
}

std::size_t SvMemoryStream::GetData( void* pData, std::size_t nCount )
{
    std::size_t nMaxCount = nEndOfData-nPos;
    if( nCount > nMaxCount )
        nCount = nMaxCount;
    if (nCount != 0)
    {
        memcpy( pData, pBuf+nPos, nCount );
    }
    nPos += nCount;
    return nCount;
}

std::size_t SvMemoryStream::PutData( const void* pData, std::size_t nCount )
{
    if( GetError() )
        return 0;

    std::size_t nMaxCount = nSize-nPos;

    // check for overflow
    if( nCount > nMaxCount )
    {
        if( nResize == 0 )
        {
            // copy as much as possible
            nCount = nMaxCount;
            SetError( SVSTREAM_OUTOFMEMORY );
        }
        else
        {
            tools::Long nNewResize;
            if( nSize && nSize > nResize )
                nNewResize = nSize;
            else
                nNewResize = nResize;

            if( (nCount-nMaxCount) < nResize )
            {
                // lacking memory is smaller than nResize,
                // resize accordingly
                if( !ReAllocateMemory( nNewResize) )
                {
                    nCount = 0;
                    SetError( SVSTREAM_WRITE_ERROR );
                }
            }
            else
            {
                // lacking memory is larger than nResize,
                // resize by (nCount-nMaxCount) + resize offset
                if( !ReAllocateMemory( nCount-nMaxCount+nNewResize ) )
                {
                    nCount = 0;
                    SetError( SVSTREAM_WRITE_ERROR );
                }
            }
        }
    }
    assert(pBuf && "Possibly Reallocate failed");
    memcpy( pBuf+nPos, pData, nCount);

    nPos += nCount;
    if( nPos > nEndOfData )
        nEndOfData = nPos;
    return nCount;
}

sal_uInt64 SvMemoryStream::SeekPos(sal_uInt64 const nNewPos)
{
    // nEndOfData: First position in stream not allowed to read from
    // nSize: Size of allocated buffer

    // check if a truncated STREAM_SEEK_TO_END was passed
    assert(nNewPos != SAL_MAX_UINT32);
    if( nNewPos < nEndOfData )
        nPos = nNewPos;
    else if( nNewPos == STREAM_SEEK_TO_END )
        nPos = nEndOfData;
    else
    {
        if( nNewPos >= nSize ) // Does buffer need extension?
        {
            if( nResize )  // Is extension possible?
            {
                tools::Long nDiff = static_cast<tools::Long>(nNewPos - nSize + 1);
                nDiff += static_cast<tools::Long>(nResize);
                ReAllocateMemory( nDiff );
                nPos = nNewPos;
                nEndOfData = nNewPos;
            }
            else  // Extension not possible, set pos to end of data
            {
                // SetError( SVSTREAM_OUTOFMEMORY );
                nPos = nEndOfData;
            }
        }
        else  // Expand buffer size
        {
            nPos = nNewPos;
            nEndOfData = nNewPos;
        }
    }
    return nPos;
}

void SvMemoryStream::FlushData()
{
}

void SvMemoryStream::ResetError()
{
    SvStream::ClearError();
}

void SvMemoryStream::AllocateMemory( std::size_t nNewSize )
{
    pBuf = new sal_uInt8[nNewSize];
}

// (using Bozo algorithm)
bool SvMemoryStream::ReAllocateMemory( tools::Long nDiff )
{
    if (!m_isWritable || !bOwnsData)
        return false;

    bool bRetVal    = false;
    tools::Long nTemp      = static_cast<tools::Long>(nSize);
    nTemp           += nDiff;
    std::size_t nNewSize  = static_cast<std::size_t>(nTemp);

    if( nNewSize )
    {
        sal_uInt8* pNewBuf   = new sal_uInt8[nNewSize];

        bRetVal = true; // Success!
        if( nNewSize < nSize )      // Are we shrinking?
        {
            memcpy( pNewBuf, pBuf, nNewSize );
            if( nPos > nNewSize )
                nPos = 0;
            if( nEndOfData >= nNewSize )
                nEndOfData = nNewSize-1;
        }
        else
        {
            if (nSize != 0)
            {
                memcpy( pNewBuf, pBuf, nSize );
            }
            memset(pNewBuf + nSize, 0x00, nNewSize - nSize);
        }

        FreeMemory();

        pBuf  = pNewBuf;
        nSize = nNewSize;
    }
    else
    {
        bRetVal = true;
        FreeMemory();
        pBuf = nullptr;
        nSize = 0;
        nEndOfData = 0;
        nPos = 0;
    }

    return bRetVal;
}

void SvMemoryStream::FreeMemory()
{
    assert(bOwnsData);
    if (bOwnsData)
    {
        delete[] pBuf;
        pBuf = nullptr;
    }
}

void* SvMemoryStream::SwitchBuffer()
{
    FlushBuffer();
    if( !bOwnsData )
        return nullptr;
    Seek( STREAM_SEEK_TO_BEGIN );

    void* pRetVal = pBuf;
    pBuf          = nullptr;
    nEndOfData    = 0;
    nResize       = 64;
    nPos          = 0;

    ResetError();

    std::size_t nInitSize = 512;
    AllocateMemory(nInitSize);
    nSize = nInitSize;

    SetBufferSize( 64 );
    return pRetVal;
}

void SvMemoryStream::SetSize(sal_uInt64 const nNewSize)
{
    if (!m_isWritable)
    {
        SetError(SVSTREAM_INVALID_HANDLE);
        return;
    }

    tools::Long nDiff = static_cast<tools::Long>(nNewSize) - static_cast<tools::Long>(nSize);
    ReAllocateMemory( nDiff );
}

void SvMemoryStream::MakeReadOnly()
{
    FlushBuffer();
    m_isWritable = false;
    nResize     = 0;
    SetBufferSize( 0 );
}

// Create an OString of nLen bytes from rStream
// coverity[ +taint_sanitize ]
OString read_uInt8s_ToOString(SvStream& rStrm, std::size_t nLen)
{
    rtl_String *pStr = nullptr;
    if (nLen)
    {
        nLen = std::min<std::size_t>(nLen, SAL_MAX_INT32);
        //limit allocation to size of file, but + 1 to set eof state
        nLen = std::min<sal_uInt64>(nLen, rStrm.remainingSize() + 1);
        //alloc a (ref-count 1) rtl_String of the desired length.
        //rtl_String's buffer is uninitialized, except for null termination
        pStr = rtl_string_alloc(sal::static_int_cast<sal_Int32>(nLen));
        SAL_WARN_IF(!pStr, "tools.stream", "allocation failed");
        if (pStr)
        {
            std::size_t nWasRead = rStrm.ReadBytes(pStr->buffer, nLen);
            if (nWasRead != nLen)
            {
                //on (typically unlikely) short read set length to what we could
                //read, and null terminate. Excess buffer capacity remains of
                //course, could create a (true) replacement OString if it matters.
                pStr->length = sal::static_int_cast<sal_Int32>(nWasRead);
                pStr->buffer[pStr->length] = 0;
            }
        }
    }

    //take ownership of buffer and return, otherwise return empty string
    return pStr ? OString(pStr, SAL_NO_ACQUIRE) : OString();
}

// Create an OUString of nLen sal_Unicode code units from rStream
// coverity[ +taint_sanitize ]
OUString read_uInt16s_ToOUString(SvStream& rStrm, std::size_t nLen)
{
    rtl_uString *pStr = nullptr;
    if (nLen)
    {
        nLen = std::min<std::size_t>(nLen, SAL_MAX_INT32);
        //limit allocation to size of file, but + 1 to set eof state
        nLen = o3tl::sanitizing_min<sal_uInt64>(nLen, (rStrm.remainingSize() + 2) / 2);
        //alloc a (ref-count 1) rtl_uString of the desired length.
        //rtl_String's buffer is uninitialized, except for null termination
        pStr = rtl_uString_alloc(sal::static_int_cast<sal_Int32>(nLen));
        SAL_WARN_IF(!pStr, "tools.stream", "allocation failed");
        if (pStr)
        {
            std::size_t nWasRead = rStrm.ReadBytes(pStr->buffer, nLen*2)/2;
            if (nWasRead != nLen)
            {
                //on (typically unlikely) short read set length to what we could
                //read, and null terminate. Excess buffer capacity remains of
                //course, could create a (true) replacement OUString if it matters.
                pStr->length = sal::static_int_cast<sal_Int32>(nWasRead);
                pStr->buffer[pStr->length] = 0;
            }
            if (rStrm.IsEndianSwap())
            {
                for (sal_Int32 i = 0; i < pStr->length; ++i)
                    pStr->buffer[i] = OSL_SWAPWORD(pStr->buffer[i]);
            }
        }
    }

    // take ownership of buffer and return, otherwise return empty string
    // coverity[tainted_data] - unhelpful untrusted loop bound
    return pStr ? OUString(pStr, SAL_NO_ACQUIRE) : OUString();
}

namespace
{
    template <typename T, typename O> T tmpl_convertLineEnd(const T &rIn, LineEnd eLineEnd)
    {
        // Determine linebreaks and compute length
        bool            bConvert    = false;    // Needs conversion
        sal_Int32       nStrLen     = rIn.getLength();
        sal_Int32       nLineEndLen = (eLineEnd == LINEEND_CRLF) ? 2 : 1;
        sal_Int32       nLen        = 0;        // Target length
        sal_Int32       i           = 0;        // Source counter

        while (i < nStrLen)
        {
            // \r or \n causes linebreak
            if ( (rIn[i] == '\r') || (rIn[i] == '\n') )
            {
                nLen = nLen + nLineEndLen;

                // If set already, skip expensive test
                if ( !bConvert )
                {
                    // Do we need to convert?
                    if ( ((eLineEnd != LINEEND_LF) && (rIn[i] == '\n')) ||
                         ((eLineEnd == LINEEND_CRLF) && (i+1) < nStrLen && (rIn[i+1] != '\n')) ||
                         ((eLineEnd == LINEEND_LF) &&
                          ((rIn[i] == '\r') || ((i+1) < nStrLen && rIn[i+1] == '\r'))) ||
                         ((eLineEnd == LINEEND_CR) &&
                          ((rIn[i] == '\n') || ((i+1) < nStrLen && rIn[i+1] == '\n'))) )
                        bConvert = true;
                }

                // skip char if \r\n or \n\r
                if ( (i+1) < nStrLen && ((rIn[i+1] == '\r') || (rIn[i+1] == '\n')) &&
                     (rIn[i] != rIn[i+1]) )
                    ++i;
            }
            else
                ++nLen;
            ++i;
        }

        if (!bConvert)
            return rIn;

        // convert linebreaks, insert string
        O aNewData(nLen);
        i = 0;
        while (i < nStrLen)
        {
            // \r or \n causes linebreak
            if ( (rIn[i] == '\r') || (rIn[i] == '\n') )
            {
                if ( eLineEnd == LINEEND_CRLF )
                {
                    aNewData.append('\r');
                    aNewData.append('\n');
                }
                else
                {
                    if ( eLineEnd == LINEEND_CR )
                        aNewData.append('\r');
                    else
                        aNewData.append('\n');
                }

                if ( (i+1) < nStrLen && ((rIn[i+1] == '\r') || (rIn[i+1] == '\n')) &&
                     (rIn[i] != rIn[i+1]) )
                    ++i;
            }
            else
            {
                aNewData.append(rIn[i]);
            }

            ++i;
        }

        return aNewData.makeStringAndClear();
    }
}

OString convertLineEnd(const OString &rIn, LineEnd eLineEnd)
{
    return tmpl_convertLineEnd<OString, OStringBuffer>(rIn, eLineEnd);
}

OUString convertLineEnd(const OUString &rIn, LineEnd eLineEnd)
{
    return tmpl_convertLineEnd<OUString, OUStringBuffer>(rIn, eLineEnd);
}

std::size_t write_uInt32_lenPrefixed_uInt16s_FromOUString(SvStream& rStrm,
                                                std::u16string_view rStr)
{
    std::size_t nWritten = 0;
    sal_uInt32 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt32>::max());
    SAL_WARN_IF(static_cast<std::size_t>(nUnits) != static_cast<std::size_t>(rStr.size()),
        "tools.stream",
        "string too long for prefix count to fit in output type");
    rStrm.WriteUInt32(nUnits);
    if (rStrm.good())
    {
        nWritten += sizeof(sal_uInt32);
        nWritten += write_uInt16s_FromOUString(rStrm, rStr, nUnits);
    }
    return nWritten;
}

std::size_t write_uInt16_lenPrefixed_uInt16s_FromOUString(SvStream& rStrm,
                                                std::u16string_view rStr)
{
    std::size_t nWritten = 0;
    sal_uInt16 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt16>::max());
    SAL_WARN_IF(nUnits != rStr.size(),
        "tools.stream",
        "string too long for prefix count to fit in output type");
    rStrm.WriteUInt16(nUnits);
    if (rStrm.good())
    {
        nWritten += sizeof(nUnits);
        nWritten += write_uInt16s_FromOUString(rStrm, rStr, nUnits);
    }
    return nWritten;
}

std::size_t write_uInt16_lenPrefixed_uInt8s_FromOString(SvStream& rStrm,
                                              std::string_view rStr)
{
    std::size_t nWritten = 0;
    sal_uInt16 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt16>::max());
    SAL_WARN_IF(static_cast<std::size_t>(nUnits) != static_cast<std::size_t>(rStr.size()),
        "tools.stream",
        "string too long for sal_uInt16 count to fit in output type");
    rStrm.WriteUInt16( nUnits );
    if (rStrm.good())
    {
        nWritten += sizeof(sal_uInt16);
        nWritten += write_uInt8s_FromOString(rStrm, rStr, nUnits);
    }
    return nWritten;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */