/*++

Microsoft Windows
Copyright (c) 1994 Microsoft Corporation.  All rights reserved.

Module Name:
    stream.cxx

Abstract:
    Implements the IStream interface on a memory buffer.

Author:
    ShannonC    09-Mar-1994

Environment:
    Windows NT and Windows 95.  We do not support DOS and Win16.

Revision History:
    12-Oct-94   ShannonC    Reformat for code review.

--*/

#include <ndrp.h>
#include <ndrole.h>

class CNdrStream : public IStream
{
public:
    virtual HRESULT STDMETHODCALLTYPE 
    QueryInterface(
        IN  REFIID riid, 
        OUT void **ppvObj);

    virtual ULONG STDMETHODCALLTYPE 
    AddRef();

    virtual ULONG STDMETHODCALLTYPE 
    Release();

    virtual HRESULT STDMETHODCALLTYPE 
    Read(
        IN  void *  pv, 
        IN  ULONG   cb, 
        OUT ULONG * pcbRead);

    virtual HRESULT STDMETHODCALLTYPE 
    Write(
        IN  void const *pv, 
        IN  ULONG       cb, 
        OUT ULONG *     pcbWritten);

    virtual HRESULT STDMETHODCALLTYPE 
    Seek(
        IN  LARGE_INTEGER   dlibMove, 
        IN  DWORD           dwOrigin, 
        OUT ULARGE_INTEGER *plibNewPosition);

    virtual HRESULT STDMETHODCALLTYPE 
    SetSize(
        IN  ULARGE_INTEGER libNewSize);

    virtual HRESULT STDMETHODCALLTYPE 
    CopyTo(
        IN  IStream *       pstm,
        IN  ULARGE_INTEGER  cb,
        OUT ULARGE_INTEGER *pcbRead,
        OUT ULARGE_INTEGER *pcbWritten);

    virtual HRESULT STDMETHODCALLTYPE 
    Commit(
        IN  DWORD grfCommitFlags);

    virtual HRESULT STDMETHODCALLTYPE 
    Revert();

    virtual HRESULT STDMETHODCALLTYPE 
    LockRegion(
        IN  ULARGE_INTEGER  libOffset,
        IN  ULARGE_INTEGER  cb,
        IN  DWORD           dwLockType);

    virtual HRESULT STDMETHODCALLTYPE 
    UnlockRegion(
        IN  ULARGE_INTEGER  libOffset,
        IN  ULARGE_INTEGER  cb,
        IN  DWORD           dwLockType);

    virtual HRESULT STDMETHODCALLTYPE 
    Stat(
        OUT STATSTG *   pstatstg, 
        IN  DWORD       grfStatFlag);

    virtual HRESULT STDMETHODCALLTYPE 
    Clone(
        OUT IStream **ppstm);

    CNdrStream(
        IN  unsigned char * pData, 
        IN  unsigned long   cbMax);

private:
    long            RefCount;
    unsigned char * pBuffer;
    unsigned long   cbBufferLength;
    unsigned long   position;
};

EXTERN_C IStream *STDAPICALLTYPE 
NdrpCreateStreamOnMemory(
    IN  unsigned char * pData, 
    IN  unsigned long   cbSize)
/*++

Routine Description:
    This function creates a stream on the specified memory buffer.

Arguments:
    pData   - Supplies pointer to memory buffer.
    cbSize  - Supplies size of memory buffer.

Return Value:
    This function returns a pointer to the newly created stream.

--*/ 
{
    CNdrStream *pStream = new CNdrStream(pData, cbSize);
    return (IStream *)pStream;
}

CNdrStream::CNdrStream(
    IN  unsigned char * pData, 
    IN  unsigned long   cbMax)
    : pBuffer(pData), cbBufferLength(cbMax)
/*++

Routine Description:
    This function creates a stream on the specified memory buffer.

Arguments:
    pData - Supplies pointer to memory buffer.
    cbMax - Supplies size of memory buffer.

Return Value:
    None.

--*/ 
{
    RefCount = 1;
    position = 0;
}


ULONG STDMETHODCALLTYPE 
CNdrStream::AddRef()
/*++

Routine Description:
    Increment the reference count.

Arguments:

Return Value:
    Reference count.

--*/ 
{
    InterlockedIncrement(&RefCount);
    return (ULONG) RefCount;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::Clone(
    OUT IStream **ppstm)
/*++

Routine Description:
    Create a new IStream object.  The new IStream gets an 
    independent seek pointer but it shares the underlying
    data buffer with the original IStream object.

Arguments:
    ppstm - Pointer to the new stream.

Return Value:
    S_OK            - The stream was successfully copied.
    E_OUTOFMEMORY   - The stream could not be copied due to lack of memory.

--*/ 
{
    HRESULT     hr;
    CNdrStream *pStream = new CNdrStream(pBuffer, cbBufferLength);

    if(pStream != 0)
    {
        pStream->position = position;
        hr = S_OK;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    *ppstm = (IStream *) pStream;

    return hr;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::Commit(
    IN DWORD grfCommitFlags)
/*++

Routine Description:
    This stream does not support transacted mode.  This function does nothing.

Arguments:
    grfCommitFlags

Return Value:
    S_OK

--*/ 
{
  return S_OK;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::CopyTo(
    IN  IStream *       pstm,
    IN  ULARGE_INTEGER  cb,
    OUT ULARGE_INTEGER *pcbRead,
    OUT ULARGE_INTEGER *pcbWritten)
/*++

Routine Description:
    Copies data from one stream to another stream.

Arguments:
    pstm        - Specifies the destination stream.
    cb          - Specifies the number of bytes to be copied to the destination stream.
    pcbRead     - Returns the number of bytes read from the source stream.
    pcbWritten  - Returns the number of bytes written to the destination stream.

Return Value:
    S_OK        - The data was successfully copied.
    Other errors from IStream::Write.

--*/ 
{
    HRESULT         hr;
    unsigned char * pSource;
    unsigned long   cbRead;
    unsigned long   cbWritten;
    unsigned long   cbRemaining;

    //Check if we are going off the end of the buffer.
    if(position < cbBufferLength)
        cbRemaining = cbBufferLength - position;
    else
        cbRemaining = 0;

    if((cb.HighPart == 0) && (cb.LowPart <= cbRemaining))
        cbRead = cb.LowPart;
    else
        cbRead = cbRemaining;

    pSource = pBuffer + position;

    //copy the data
    hr = pstm->Write(pSource, cbRead, &cbWritten);

    //advance the current position
    position += cbRead;

    if (pcbRead != 0)
    {
        pcbRead->LowPart = cbRead;
        pcbRead->HighPart = 0;
    }
    if (pcbWritten != 0)
    {
        pcbWritten->LowPart = cbWritten;
        pcbWritten->HighPart = 0;
    }

    return hr;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::LockRegion(
    IN ULARGE_INTEGER   libOffset,
    IN ULARGE_INTEGER   cb,
    IN DWORD            dwLockType)
/*++

Routine Description:
    Range locking is not supported by this stream.

Return Value:
    STG_E_INVALIDFUNCTION.

--*/ 
{
    return STG_E_INVALIDFUNCTION;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::QueryInterface( 
    REFIID riid, 
    void **ppvObj)
/*++

Routine Description:
    Query for an interface on the stream.  The stream supports
    the IUnknown and IStream interfaces.

Arguments:
    riid        - Supplies the IID of the interface being requested.
    ppvObject   - Returns a pointer to the requested interface.

Return Value:
    S_OK
    E_NOINTERFACE

--*/ 
{
    HRESULT hr;

    if ((memcmp(&riid, &IID_IUnknown, sizeof(IID)) == 0) ||
       (memcmp(&riid, &IID_IStream, sizeof(IID)) == 0))
    {
        this->AddRef();
        *ppvObj = (IStream *) this;
        hr = S_OK;
    }
    else
    {
        *ppvObj = 0;
        hr = E_NOINTERFACE;
    }
    
    return hr;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::Read(
    OUT void * pv,
    IN  ULONG  cb, 
    OUT ULONG *pcbRead)
/*++

Routine Description:
    Reads data from the stream starting at the current seek pointer.

Arguments:
    pv      - Returns the data read from the stream.
    cb      - Supplies the number of bytes to read from the stream.
    pcbRead - Returns the number of bytes actually read from the stream.

Return Value:
    S_OK    - The data was successfully read from the stream.
    S_FALSE - The number of bytes read was smaller than the number requested.

--*/ 
{
    HRESULT         hr;
    unsigned long   cbRead;
    unsigned long   cbRemaining;

    //Check if we are reading past the end of the buffer.
    if(position < cbBufferLength)
        cbRemaining = cbBufferLength - position;
    else
        cbRemaining = 0;

    if(cb <= cbRemaining)
    {
        cbRead = cb;
        hr = S_OK;
    }
    else
    {
        cbRead = cbRemaining;
        hr = S_FALSE;
    }

    //copy the data
    RpcpMemoryCopy(pv, pBuffer + position, cbRead);

    //advance the current position
    position += cbRead;
    
    if(pcbRead != 0)
        *pcbRead = cbRead;

    return hr;
}

ULONG STDMETHODCALLTYPE 
CNdrStream::Release()
/*++

Routine Description:
    Decrement the reference count.  When the reference count
    reaches zero, the stream is deleted.

Arguments:

Return Value:
    Reference count.

--*/ 
{
    unsigned long count;

    count = RefCount - 1;
    if(InterlockedDecrement(&RefCount) == 0)
    {
        count = 0;
        delete this;
    }

    return count;
}


HRESULT STDMETHODCALLTYPE 
CNdrStream::Revert()
/*++

Routine Description:
    This stream does not support transacted mode.  This function does nothing.

Arguments:
    None.

Return Value:
    S_OK.

--*/ 
{
  return S_OK;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::Seek(
    IN  LARGE_INTEGER   dlibMove,
    IN  DWORD           dwOrigin,
    OUT ULARGE_INTEGER *plibNewPosition)
/*++

Routine Description:
    Sets the position of the seek pointer.  It is an error to seek
    before the beginning of the stream or past the end of the stream.

Arguments:
    dlibMove        - Supplies the offset from the position specified in dwOrigin.
    dwOrigin        - Supplies the seek mode.
    plibNewPosition - Returns the new position of the seek pointer.

Return Value:
    S_OK                    - The seek pointer was successfully adjusted.
    STG_E_INVALIDFUNCTION   - dwOrigin contains invalid value.
    STG_E_SEEKERROR         - The seek pointer cannot be positioned before the
                              beginning of the stream or past the 
                              end of the stream.

--*/ 
{
    HRESULT         hr;
    long            high;
    long            low;
    unsigned long   offset;
    unsigned long   cbRemaining;

    switch (dwOrigin)
    {
    case STREAM_SEEK_SET:
        //Set the seek position relative to the beginning of the stream.
        if((dlibMove.HighPart == 0) && (dlibMove.LowPart <= cbBufferLength))
        {
            position = dlibMove.LowPart;
            hr = S_OK;
        }
        else
        {
            //It is an error to seek past the end of the stream.
            hr = STG_E_SEEKERROR;
        }
        break;

    case STREAM_SEEK_CUR:
        //Set the seek position relative to the current position of the stream.
        high = (long) dlibMove.HighPart;
        if(high < 0)
        {
            //Negative offset
            low = (long) dlibMove.LowPart;
            offset = -low;
            
            if((high == -1) && (offset <= position))
            {
                position -= offset;
                hr = S_OK;
            }
            else
            {
                //It is an error to seek before the beginning of the stream.
                hr = STG_E_SEEKERROR;
            }
        }
        else
        {
            //Positive offset
            if(position < cbBufferLength)
                cbRemaining = cbBufferLength - position;
            else
                cbRemaining = 0;
    
            if((dlibMove.HighPart == 0) && (dlibMove.LowPart <= cbRemaining))
            {
                position += dlibMove.LowPart;
                hr = S_OK;
            }
            else
            {
                //It is an error to seek past the end of the stream.
                hr = STG_E_SEEKERROR;
            }
        }
        break;

    case STREAM_SEEK_END:
    //Set the seek position relative to the end of the stream.
        high = (long) dlibMove.HighPart;
        if(high < 0)
        {
            //Negative offset
            low = (long) dlibMove.LowPart;
            offset = -low;
            
            if((high == -1) && (offset <= cbBufferLength))
            {
                position = cbBufferLength - offset;
                hr = S_OK;
            }
            else
            {
                //It is an error to seek before the beginning of the stream.
                hr = STG_E_SEEKERROR;
            }
        }
        else if(dlibMove.QuadPart == 0)
        {
            position = cbBufferLength;
            hr = S_OK;
        }
        else
        {
            //Positive offset
            //It is an error to seek past the end of the stream.
            hr = STG_E_SEEKERROR;
        }
        break;

    default:
        //dwOrigin contains an invalid value.
        hr = STG_E_INVALIDFUNCTION;
    }

    if (plibNewPosition != 0)
    {
        plibNewPosition->LowPart = position;
        plibNewPosition->HighPart = 0;
    }

    return hr;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::SetSize(
    IN ULARGE_INTEGER libNewSize)
/*++

Routine Description:
    Changes the size of the stream.  

Arguments:
    libNewSize - Supplies the new size of the stream.

Return Value:
    S_OK                - The stream size was successfully changed.
    STG_E_MEDIUMFULL    - The stream size could not be changed.

--*/ 
{
    HRESULT hr;

    if((libNewSize.HighPart == 0) && (libNewSize.LowPart <= cbBufferLength))
    {
        cbBufferLength = libNewSize.LowPart;
        hr = S_OK;
    }
    else
    {
        hr = STG_E_MEDIUMFULL;
    }

    return hr;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::Stat(
    OUT STATSTG *   pstatstg, 
    IN  DWORD       grfStatFlag)
/*++

Routine Description:
    This function gets information about this stream.

Arguments:
    pstatstg    - Returns information about this stream.
    grfStatFlg  - Specifies the information to be returned in pstatstg.

Return Value:
    S_OK.

--*/ 
{
    memset(pstatstg, 0, sizeof(STATSTG));
    pstatstg->type = STGTY_STREAM;
    pstatstg->cbSize.LowPart = cbBufferLength;
    pstatstg->cbSize.HighPart = 0;
       
    return S_OK;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::UnlockRegion(
    IN ULARGE_INTEGER   libOffset,
    IN ULARGE_INTEGER   cb,
    IN DWORD            dwLockType)
/*++

Routine Description:
    Range locking is not supported by this stream.

Return Value:
    STG_E_INVALIDFUNCTION.

--*/ 
{
    return STG_E_INVALIDFUNCTION;
}

HRESULT STDMETHODCALLTYPE 
CNdrStream::Write(
    IN  void const *pv,
    IN  ULONG       cb,
    OUT ULONG *     pcbWritten)
/*++

Routine Description:
    Write data to the stream starting at the current seek pointer.

Arguments:
    pv          - Supplies the data to be written to the stream.
    cb          - Specifies the number of bytes to be written to the stream.
    pcbWritten  - Returns the number of bytes actually written to the stream.

Return Value:
    S_OK                - The data was successfully written to the stream.
    STG_E_MEDIUMFULL    - Data cannot be written past the end of the stream.

--*/ 
{
    HRESULT         hr;
    unsigned long   cbRemaining;
    unsigned long   cbWritten;

    //Check if we are writing past the end of the buffer.
    if(position < cbBufferLength)
        cbRemaining = cbBufferLength - position;
    else
        cbRemaining = 0;
    
    if(cb <= cbRemaining)
    {
        cbWritten = cb;
        hr = S_OK;
    }
    else
    {
        cbWritten = cbRemaining;
        hr = STG_E_MEDIUMFULL;
    }

    // Write the data.
    RpcpMemoryCopy(pBuffer + position, pv, cbWritten);

    //Advance the current position
    position += cbWritten;

    //update pcbWritten
    if (pcbWritten != 0)
        *pcbWritten = cbWritten;

    return hr;
}
