//
//  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

//////////////////////////////////////////////////////////////////////
// C7kSocketProtocol class implementation.

C7kSocketProtocol::C7kSocketProtocol( const unsigned long rulMaxPacketBytes )

                  :m_ulHeaderSize       ( tagNETWORKFRAMEHEADER::Size() ),
                   m_ulMaxPacketSize    ( rulMaxPacketBytes ),
                   m_unProtocolVersion  ( un7kSocketProtocolVersion_c )

{
    m_pDecoder = std::auto_ptr<CDecode>( new CDecode );
    ASSERT( m_pDecoder.get() != NULL );

    m_pEncoder = std::auto_ptr<CEncode>( new CEncode( m_ulMaxPacketSize ) );
    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_eCurrentState             = 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 a simple state machine to handle accumulation of sequenced frames.
    // Note also that socket data are read as header then body, hence the split processing below.

    // State transition table is as follows:
    //
    // Current State    Next State          Transition Event
    // -------------    ----------          ----------------
    //
    // stateIdle        stateIdle           Ill-sequenced record.
    // stateIdle        stateAccumulate     First record in sequence.
    // stateAccumulate  stateAccumulate     Next record in sequence.
    // stateAccumulate  stateDone           Last record in sequence.
    // stateIdle        stateDone           Record is complete sequence.
    // stateDone        stateIdle           Following DONE state processing     (Immediate transition).
    // stateAccumulate  stateReset          Sequence error.
    // stateReset       stateIdle           Following RESET state processing    (Immediate transition).

    bool bSuccess = true;

    try
    {
        // If it is a header, decode the frame and determine the pending transition event.

        if ( rbIsHeader )
        {
            if ( ! C7kSocketProtocol::IsValidHeader( pbyFrame, rulBytes ) )
            {
                ThrowMessage_m( "Header is invalid" );
            }

            PNETWORKFRAMEHEADER psHeader = reinterpret_cast<PNETWORKFRAMEHEADER> ( const_cast<PBYTE>( pbyFrame ) );

            m_ulDynamicBytesThisFrame = psHeader->m_ulPacketSize - tagNETWORKFRAMEHEADER::Size();

            // Note: the 6046 ASSUMES ONE 7K RECORD WILL BE ENCODED PER SERIES OF PACKETS.
            // If this changes in the future, then the members identified below (pertaining to the v2 protocol)
            // will need to be considered when reading the socket data and handled and dispatched accordingly. 
            // The implication of this is that this routine won't change, rather, the socket reading code will.
            // Version 2 members are:
            //
            // unsigned short  m_unTotalRecords;            // Total number of records in network packets transmitted (helper field for parsing data). Max 128 records per transmission.
            // unsigned short  m_unTransmissionIdentifier;  // Helper field for packet assembly. Must be the same number for each network packet in transmission. Adjacent transmissions in time from one source may not use the same id.
            // unsigned long   m_ulTotalSize;               // Total size in bytes of all packets in transmission excluding network frame(s).

            // Detect the pending state transition event based on the record component.

            switch ( m_eCurrentState )
            {
                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_eCurrentState = stateDone;   
                        }
                        else
                        {
                            m_eCurrentState = stateAccumulate;
                        }
                    }
                    else
                    {
                        m_eCurrentState = stateIdle;
                    }

                    break;

                case stateAccumulate:

                    // In Accumulate mode, there are three permissable transition events, namely:
                    //
                    // 1) last packet in sequence detected
                    //      (therefore transition to done state; that is, accumlate remainder of message 
                    //       and transition back to idle).
                    //
                    // 2) next packet in sequence detected
                    //      (therefore remain accumulating).
                    //
                    // 3) next packet is out of sequence
                    //      (therefore transition to reset state).

                    if ( psHeader->m_ulSequenceNumber == ( psHeader->m_ulTotalPackets - 1UL ) )
                    {
                        m_eCurrentState = stateDone;                                                       // Sequence is complete.
                    }
                    else if ( psHeader->m_ulSequenceNumber == m_ulLastSequenceNumber + 1UL )
                    {
                        m_eCurrentState = stateAccumulate;                                                 // Stay in accumulate mode.
                    }
                    else
                    {
                        m_eCurrentState = stateReset;                                                      // Miss-sequence so reset.
                    }

                    break;
            }
        }
        else
        {
            // Here, we're processing the data portion of the message -- we handle the state processing,
            // and immediate transitions to those states identified in the transition table above.

            switch ( m_eCurrentState )
            {
                case stateIdle:

                    // Ignore the data since detection of the next record is pending.

                    break;

                case stateAccumulate:

                    // Accumulate the data and remain accumulating.

                    Add( const_cast<PBYTE>( pbyFrame ), rulBytes );
                    break;

                case stateDone:

                    // Last packet in sequence or a packet contains a complete record; therefore accumulate
                    // the data and transition back to idle state in preparation for the start of the next sequence.

                    Add( const_cast<PBYTE>( pbyFrame ), rulBytes );
                    m_bRecordIsReady = true;
                    m_eCurrentState = stateIdle;
                    break;

                case stateReset:

                    // A sequence error was detected therefore reset the accumulator etc. and transition back to 
                    // the idle state in preparation for the start of the next sequence.

                    Reset();    // Reset accumulator and set idle state.
                    break;
            }
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        Reset();    // Reset accumulator and set idle state.
        bSuccess = false;
        TRACE( _T( "C7kSocketProtocol::CDecode::ProcessFrame(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        Reset();    // Reset accumulator and set idle state.
        bSuccess = false;
        TRACE( _T( "C7kSocketProtocol::CDecode::ProcessFrame(), unspecified exception caught\n" ) );
    }

    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,
                                        unsigned short  unTotalRecords,
                                        unsigned short  unTransmissionId )
{
    bool bSuccess = false;

    ASSERT( m_pEncoder.get() != NULL );

    if ( m_pEncoder.get() != NULL )
    {
        bSuccess = m_pEncoder->EncodePackets( pbyStream, ulNumBytes, m_ulMaxPacketSize, m_unProtocolVersion, unTotalRecords, unTransmissionId );
    }

    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          ( const unsigned long rulMaxPacketBytes )
                           :m_ulMaxPacketSize( rulMaxPacketBytes )
{
    ResetEncoder();
}

inline
C7kSocketProtocol::CEncode::~CEncode( void )
{
    ResetEncoder();
}

inline
void C7kSocketProtocol::CEncode::ResetEncoder( void )
{
    Reset();

    m_PacketIndex.clear();
    m_sPacketHeader.Reset();
    m_pLastRetrievedPacket = m_PacketIndex.end();

    m_ulNumberOfPackets = 0UL;

    memset( &m_sPacketHeader, 0x00, tagNETWORKFRAMEHEADER::Size() );
}

inline
bool C7kSocketProtocol::CEncode::EncodePackets( BYTE           *pbyStream,
                                                unsigned long   ulNumBytes,
                                                unsigned long   ulMaxPacketSize,
                                                unsigned short  unProtocolVersion,
                                                unsigned short  unTotalRecords,
                                                unsigned short  unTransmissionId )
{
    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 to be set when packets are fragmented.
        //if ( ulNumPackets > 1UL )
        //{
        //    TRACE( "Packets: %lu\n", ulNumPackets );
        //}
        //#endif

        if ( ulNumPackets > 0UL )
        {
            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, ulNumBytes, unTotalRecords, unTransmissionId );

                if ( ! bEncoded )
                {
                    TRACE( _T( "CEncode::EncodePackets(), m_Encoder.EncodePacket() error\n" ) );
                    break;
                }
            }

            // Only now should we set the retrieve iterator since manipulation of the queue will likely invalidate it.

            if ( bEncoded && ( ! m_PacketIndex.empty() ) )
            {
                m_pLastRetrievedPacket = m_PacketIndex.begin();
            }
            else
            {
                m_pLastRetrievedPacket = m_PacketIndex.end();
            }

        }
    }

    return bEncoded;
}

inline
bool C7kSocketProtocol::CEncode::EncodeNextPacket(  BYTE              *&rpbyPacket,
                                                    unsigned long      *pulBytesLeft,
                                                    unsigned long       ulPacket,
                                                    unsigned long       ulMaxPacketDataSize,
                                                    unsigned short      unProtocolVersion,
                                                    unsigned long       ulTotalEmbeddedBytes,
                                                    unsigned short      unTotalRecords,
                                                    unsigned short      unTransmissionId,
                                                    unsigned long       ulDestinationDeviceIdentifier,
                                                    unsigned short      unDestinationEnumerator,
                                                    unsigned short      unSourceEnumerator,
                                                    unsigned long       ulSourceDeviceIdentifier )
{
    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 ( ( rpbyPacket != 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.

#if ( NF_VERSION_7K == 1 )

        UNREFERENCED_PARAMETER( unTotalRecords );
        UNREFERENCED_PARAMETER( unTransmissionId );
        UNREFERENCED_PARAMETER( ulTotalEmbeddedBytes );

        UNREFERENCED_PARAMETER( ulDestinationDeviceIdentifier );
        UNREFERENCED_PARAMETER( unDestinationEnumerator );
        UNREFERENCED_PARAMETER( unSourceEnumerator );
        UNREFERENCED_PARAMETER( ulSourceDeviceIdentifier );

#else

        // Version 2 fields.

        m_sPacketHeader.m_unTotalRecords                = unTotalRecords;                       // Total number of records in network packets transmitted (helper field for parsing data). Max 128 records per transmission.
        m_sPacketHeader.m_unTransmissionIdentifier      = unTransmissionId;                     // Helper field for packet assembly. Must be the same number for each network packet in transmission. Adjacent transmissions in time from one source may not use the same id.
        m_sPacketHeader.m_ulTotalSize                   = ulTotalEmbeddedBytes;                 // Total size in bytes of all packets in transmission excluding network frame(s).

#if ( NF_VERSION_7K > 2 )

        m_sPacketHeader.m_ulDestinationDeviceIdentifier = ulDestinationDeviceIdentifier;
        m_sPacketHeader.m_unDestinationEnumerator       = unDestinationEnumerator;
        m_sPacketHeader.m_unSourceEnumerator            = unSourceEnumerator;
        m_sPacketHeader.m_ulSourceDeviceIdentifier      = ulSourceDeviceIdentifier;

#else

        UNREFERENCED_PARAMETER( ulDestinationDeviceIdentifier );
        UNREFERENCED_PARAMETER( unDestinationEnumerator );
        UNREFERENCED_PARAMETER( unSourceEnumerator );
        UNREFERENCED_PARAMETER( ulSourceDeviceIdentifier );

#endif
#endif

        // 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), tagNETWORKFRAMEHEADER::Size() );

        // Format the packet stream data; first, though, save its index to aid later extraction.

        PACKETINDEX sPacketIndex( m_sPacketHeader.m_ulPacketSize, ulStartOfPacketInBuffer );
        m_PacketIndex.push_back( sPacketIndex );

        Add( rpbyPacket, 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

        rpbyPacket    += ulStreamBytesToEncode;
        *pulBytesLeft -= ulStreamBytesToEncode;

        bEncoded = true;
    }

    return bEncoded;
}

inline
unsigned long C7kSocketProtocol::CEncode::NumberOfPackets( void ) const
{
#ifdef _DEBUG
    if ( m_ulNumberOfPackets != ((unsigned long) m_PacketIndex.size()) )
    {
        TRACE( _T( "Packets: %lu, Size: %u.\n" ), m_ulNumberOfPackets, m_PacketIndex.size() );
        ASSERT( false );
    }
#endif

    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->m_ulPacketSizeInBytes,
                                  m_pLastRetrievedPacket->m_ulBufferOffset );

        m_pLastRetrievedPacket++;

        rulBytesInPacket = sPacketIndex.m_ulPacketSizeInBytes;
        pbyStartOfPacket = GetAt( sPacketIndex.m_ulBufferOffset );
    }

    return pbyStartOfPacket;
}

