//
//  Copyright © 2001 - 2002, RESON Inc. All Rights Reserved.
//
//  No part of this file may be reproduced or transmitted in any form or by
//  any means, electronic or mechanical, including photocopy, recording, or
//  information storage or retrieval system, without permission in writing
//  from RESON Inc.
//
//  Filename:   C7kSocketProtocol.cpp
//
//  Project:    6046.
//
//  Author(s):  W. Arcus
//
//  Purpose:    Class implementation file for C7kSocketProtocol and supporting helpers.
//
//  Notes:      
//

#include "StdAfx.h"
#include "NetUtils.h"
#include "7kSocketProtocol.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

const unsigned long ulMaxPacketSizeInBytes_c = 64UL * 1024UL;         // 7k network packet (UDP and TCP) are to be <= 64k

//////////////////////////////////////////////////////////////////////
// C7kSocketProtocol class implementation.

C7kSocketProtocol::C7kSocketProtocol( void )

                  :m_ulHeaderSize       ( tagNETWORKFRAMEHEADER::Size() ),
                   m_ulMaxPacketSize    ( ulMaxPacketSizeInBytes_c ),
                   m_unProtocolVersion  ( unDefault7kProtocolVersion_c )

{
    m_pDecoder = std::auto_ptr<CDecode>( new CDecode );
    ASSERT( m_pDecoder.get() != NULL );

    m_pEncoder = std::auto_ptr<CEncode>( new CEncode );
    ASSERT( m_pEncoder.get() != NULL );
}

C7kSocketProtocol::~C7kSocketProtocol( void )
{
    DestroyAutoPointer( m_pEncoder );
    DestroyAutoPointer( m_pDecoder );
}

//////////////////////////////////////////////////////////////////////
// Public methods for decoding.

bool C7kSocketProtocol::ResetDecoder( void )
{
    ASSERT( m_pDecoder.get() != NULL );

    if ( m_pDecoder.get() != NULL )
    {
        m_pDecoder->Reset();
        return true;
    }

    return false;
}

unsigned long C7kSocketProtocol::GetHeaderSize( void )
{
    return tagNETWORKFRAMEHEADER::Size();
}

bool C7kSocketProtocol::IsValidHeader(  const BYTE          *pbyStream,
                                        const unsigned long &rulNumBytes )
{
    bool bValidHeader = false;

    const unsigned long ulHeaderSize = GetHeaderSize();

    ASSERT( pbyStream   != NULL );
    ASSERT( rulNumBytes >= ulHeaderSize );

    if ( ( pbyStream   != NULL         ) &&
         ( rulNumBytes >= ulHeaderSize )  )
    {
        bValidHeader = ((NETWORKFRAMEHEADER *) pbyStream)->IsValid();
    }

    return bValidHeader;
}

bool C7kSocketProtocol::Add(    const BYTE          *pbyStream,
                                const unsigned long &rulBytes,
                                const bool          &rbIsHeader )
{
    bool bSuccess = false;      // Assume failure for now.

    ASSERT( m_pDecoder.get() != NULL );

    if ( m_pDecoder.get() != NULL )
    {
        bSuccess = m_pDecoder->ProcessFrame( pbyStream, rulBytes, rbIsHeader );
    }

    return bSuccess;
}

unsigned long C7kSocketProtocol::BytesFollowingHeader( void ) const
{
    unsigned long ulBytesFollowingHeader = 0UL;

    ASSERT( m_pDecoder.get() != NULL );

    if ( m_pDecoder.get() != NULL )
    {
        ulBytesFollowingHeader = m_pDecoder->DynamicBytesThisFrame();
    }

    return ulBytesFollowingHeader;
}
                                              
bool C7kSocketProtocol::IsRecordComplete( void ) const
{
    bool bRecordComplete = false;

    ASSERT( m_pDecoder.get() != NULL );

    if ( m_pDecoder.get() != NULL )
    {
        bRecordComplete = m_pDecoder->IsRecordComplete();
    }

    return bRecordComplete;
}

BYTE * C7kSocketProtocol::DecodedRecord( void )
{
    ASSERT( m_pDecoder.get() != NULL );

    if ( m_pDecoder.get() != NULL )
    {
        return m_pDecoder->GetAt( 0 );
    }

    return NULL;
}

unsigned long C7kSocketProtocol::DecodedRecordBytes( void )
{
    ASSERT( m_pDecoder.get() != NULL );

    if ( m_pDecoder.get() != NULL )
    {
        return ( sizeof( BYTE ) * m_pDecoder->Size() );
    }

    return 0UL;
}

//////////////////////////////////////////////////////////////////////
// C7kSocketProtocol::CDecode class implementation - a private helper of C7kSocketProtocol

inline
C7kSocketProtocol::CDecode::CDecode( void )
{
    Reset();
}

inline
C7kSocketProtocol::CDecode::~CDecode( void )
{
    Reset();
}

inline
void C7kSocketProtocol::CDecode::Reset( void )
{
    m_bRecordIsReady            = false;
    m_eState                    = stateIdle;
    m_ulLastSequenceNumber      = 0UL;
    m_ulDynamicBytesThisFrame   = 0UL;

    CDynamicBuffer<BYTE>::Reset();
}

inline
bool C7kSocketProtocol::CDecode::ProcessFrame(  const BYTE          *pbyFrame,
                                                const unsigned long &rulBytes,
                                                const bool          &rbIsHeader )
{
    // Implements the state machine to handle accumulation of sequenced frames. Note: data
    // will be read header then body from socket hence state detection and body handled in seperate invokations.

    bool bSuccess = true;

    if ( rbIsHeader )
    {
        if ( C7kSocketProtocol::IsValidHeader( pbyFrame, rulBytes ) )
        {
            NETWORKFRAMEHEADER *psHeader = (NETWORKFRAMEHEADER *) ( pbyFrame );

            m_ulDynamicBytesThisFrame = psHeader->m_ulPacketSize - tagNETWORKFRAMEHEADER::Size();

            switch ( m_eState )
            {
                case stateIdle:

                    // Wait for the first message in the next sequence and when found,
                    // put the state machine straight into accumulate mode.

                    if ( psHeader->m_ulSequenceNumber == 0UL )
                    {
                        if ( psHeader->m_ulTotalPackets == 1UL )
                        {
                            m_eState = stateDone;   
                        }
                        else
                        {
                            m_eState = stateAccumulate;
                        }
                    }

                    break;

                case stateAccumulate:

                    // If we're accumulating then there are three permissable state transitions, namely:
                    //
                    // 1) last packet found in sequence therefore were done (ie accumlate remainder of message and transition back to idle).
                    // 2) next packet in sequence therefore remain accumulating.
                    // 3) next packet is out of sequence therefore reset and go back to idle mode ready for next sequence to begin.

                    if ( psHeader->m_ulSequenceNumber == ( psHeader->m_ulTotalPackets - 1UL ) )
                    {
                        m_eState = stateDone;                                                       // Sequence is complete.
                    }
                    else if ( psHeader->m_ulSequenceNumber == m_ulLastSequenceNumber + 1UL )
                    {
                        m_eState = stateAccumulate;                                                 // Stay in accumulate mode.
                    }
                    else
                    {
                        m_eState = stateReset;                                                      // Miss-sequence so reset.
                    }

                    break;
            }

            m_ulLastSequenceNumber = psHeader->m_ulSequenceNumber;
        }
        else
        {
            bSuccess = false;
            m_eState = stateIdle;
            m_ulDynamicBytesThisFrame = 0UL;
            TRACE( _T( "C7kSocketProtocol::CDecode::ProcessFrame(), Invalid header\n" ) );
        }
    }
    else
    {
        switch ( m_eState )
        {
            case stateIdle:
                break;

            case stateAccumulate:

                Add( const_cast<BYTE *>( pbyFrame ), rulBytes );
                break;

            case stateDone:

                Add( const_cast<BYTE *>( pbyFrame ), rulBytes );
                m_bRecordIsReady = true;
                m_eState = stateIdle;
                break;

            case stateReset:

                Reset();
                m_eState = stateIdle;
                break;
        }
    }

    return bSuccess;
}

inline
unsigned long C7kSocketProtocol::CDecode::DynamicBytesThisFrame( void ) const
{
    return m_ulDynamicBytesThisFrame;
}

inline
bool C7kSocketProtocol::CDecode::IsRecordComplete( void ) const
{
    return m_bRecordIsReady;
}

//////////////////////////////////////////////////////////////////////
// Public methods for encoding.

bool C7kSocketProtocol::EncodePackets(  BYTE           *pbyStream,
                                        unsigned long   ulNumBytes )
{
    bool bSuccess = false;

    ASSERT( m_pEncoder.get() != NULL );

    if ( m_pEncoder.get() != NULL )
    {
        bSuccess = m_pEncoder->EncodePackets( pbyStream, ulNumBytes, m_ulMaxPacketSize, m_unProtocolVersion );
    }

    return bSuccess;
}

unsigned long C7kSocketProtocol::GetNumberOfPackets( void )
{
    unsigned long ulNumberOfPackets = 0UL;

    ASSERT( m_pEncoder.get() != NULL );

    if ( m_pEncoder.get() != NULL )
    {
        ulNumberOfPackets = m_pEncoder->NumberOfPackets();
    }

    return ulNumberOfPackets;
}

BYTE * C7kSocketProtocol::GetNextPacket( unsigned long &rulBytesInPacket )
{
    rulBytesInPacket = 0UL;

    BYTE *pbyPacket  = NULL;

    ASSERT( m_pEncoder.get() != NULL );

    if ( m_pEncoder.get() != NULL )
    {
        pbyPacket = m_pEncoder->GetNextPacket( rulBytesInPacket );
    }

    return pbyPacket;
}

///////////////////////////////////////////////////////////////////////////////
// Private encoding helpers.
//

C7kSocketProtocol::CEncode::CEncode( void )
                  :m_ulMaxPacketSize( ulMaxPacketSizeInBytes_c ) 
{
    ResetEncoder();
}

inline
C7kSocketProtocol::CEncode::~CEncode( void )
{
    ResetEncoder();
}

inline
void C7kSocketProtocol::CEncode::ResetEncoder( void )
{
    m_PacketIndex.clear();
    m_sPacketHeader.Reset();

    Reset();

    m_ulNumberOfPackets = 0UL;
}

inline
bool C7kSocketProtocol::CEncode::EncodePackets( BYTE           *pbyStream,
                                                unsigned long   ulNumBytes,
                                                unsigned long   ulMaxPacketSize,
                                                unsigned short  unProtocolVersion )
{
    bool bEncoded = false;      // Assume failure for now.

    if ( ( pbyStream != NULL ) && ( ulNumBytes > 0UL ) )
    {
        // Clear the stream Fifo, the indicies into it and the packet header
        // in this series of encoded sequence.

        ResetEncoder();

        // Compute the number of packets based on the size of the raw un-encoded stream and the
        // maximum allowed data portion of a network packet.

        const unsigned long ulMaxDataSize = ulMaxPacketSize - tagNETWORKFRAMEHEADER::Size();
        unsigned long       ulNumPackets  = ulNumBytes / ulMaxDataSize;

        if ( ( ulNumBytes % ulMaxDataSize ) != 0UL )
        {
            ++ulNumPackets;
        }

//#if _DEBUG  // TESTCODE - used to allow a break point when packets are fragmented.
//        if ( ulNumPackets > 1UL )
//        {
//            TRACE( "Packets: %lu\n", ulNumPackets );
//        }
//#endif

        m_sPacketHeader.m_ulTotalPackets = m_ulNumberOfPackets = ulNumPackets;

        BYTE           *pbyPacket   = pbyStream;
        unsigned long   ulBytesLeft = ulNumBytes;

        // Encode each frame (ie, header and dynamic data).

        for ( unsigned long ulPacket = 0UL; ulPacket < ulNumPackets; ulPacket++ )
        {
            bEncoded = EncodeNextPacket( &pbyPacket, &ulBytesLeft, ulPacket, ulMaxDataSize, unProtocolVersion );

            if ( bEncoded )
            {
                if ( ulPacket == 0UL )
                {
                    m_pLastRetrievedPacket = m_PacketIndex.begin();
                }
            }
            else
            {
                TRACE( _T( "CEncode::EncodePackets(), m_Encoder.EncodePacket() error\n" ) );
                break;
            }
        }
    }

    return bEncoded;
}

inline
bool C7kSocketProtocol::CEncode::EncodeNextPacket(  BYTE              **ppbyPacket,
                                                    unsigned long      *pulBytesLeft,
                                                    unsigned long       ulPacket,
                                                    unsigned long       ulMaxPacketDataSize,
                                                    unsigned short      unProtocolVersion )

{
    bool bEncoded = false;           // Assume failure for now.

    // For each packet, format the header and stream bytes then set the indicies to the packet
    // to point into the fifo.

    if ( ( *ppbyPacket != NULL ) && ( *pulBytesLeft != 0 ) )
    {
        // Determine the number of data bytes to encode in this packet.

        const unsigned long ulStreamBytesToEncode = min( *pulBytesLeft, ulMaxPacketDataSize );

        // Format the header for this packet.

        m_sPacketHeader.m_ulPacketSize      = m_sPacketHeader.Size() + ulStreamBytesToEncode;   // Size in bytes of this packet including the header and appended data.
        m_sPacketHeader.m_ulSequenceNumber  = ulPacket;                                         // Sequential packet number; allows correct ordering during reconstruction. Range: 0 (zero) to N - 1 packets.
        m_sPacketHeader.m_unVersion         = unProtocolVersion;                                // Version of this frame (e.g.: 1, 2, …)
        m_sPacketHeader.m_unOffset          = m_sPacketHeader.Size();                           // Offset, in bytes, to the start of data from the start of this packet.

        // Append the header.

        unsigned long ulStartOfPacketInBuffer = Size();                                         // Size of dynamic buffer (from base class).

        // Add the network header to the message.

        Add( reinterpret_cast<BYTE *>(&m_sPacketHeader), m_sPacketHeader.Size() );

        // Format the packet stream data but first, save its index into the Fifo for later extraction.

        PACKETINDEX sPacketIndex( m_sPacketHeader.m_ulPacketSize, GetAt( ulStartOfPacketInBuffer ) );

        m_PacketIndex.push_back( sPacketIndex );

        Add( &(*ppbyPacket)[ 0 ], ulStreamBytesToEncode );

#if 0   // TESTCODE
        {
            NETWORKFRAMEHEADER * pHeader = (NETWORKFRAMEHEADER *) GetAt( ulStartOfPacketInBuffer );

            TRACE( _T( "Encoded Packet, Offset: %u, Sequence No: %lu\n" ), pHeader->m_unOffset,
                                                                           pHeader->m_ulSequenceNumber );
        }
#endif

        *ppbyPacket   += ulStreamBytesToEncode;
        *pulBytesLeft -= ulStreamBytesToEncode;

        bEncoded = true;
    }

    return bEncoded;
}

inline
unsigned long C7kSocketProtocol::CEncode::NumberOfPackets( void ) const
{
    ASSERT( m_ulNumberOfPackets == ((unsigned long) m_PacketIndex.size()) );

    return m_ulNumberOfPackets;
}

inline
BYTE * C7kSocketProtocol::CEncode::GetNextPacket( unsigned long &rulBytesInPacket )
{
    BYTE   *pbyStartOfPacket = NULL;

    rulBytesInPacket = 0UL;

    if ( m_pLastRetrievedPacket != m_PacketIndex.end() )
    {
        PACKETINDEX sPacketIndex( m_pLastRetrievedPacket->ulPacketSizeInBytes,
                                  m_pLastRetrievedPacket->pbyStartOfPacketInFifo );

        m_pLastRetrievedPacket++;

        rulBytesInPacket = sPacketIndex.ulPacketSizeInBytes;
        pbyStartOfPacket = sPacketIndex.pbyStartOfPacketInFifo;
    }

    return pbyStartOfPacket;
}

