cbs/CbsServer/ServerSrc/Ccbsreccollector.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 01:11:09 +0200
changeset 0 ff3b6d0fd310
permissions -rw-r--r--
Revision: 201003 Kit: 201005

/*
* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  This module contains the implementation of CCbsRecDecoder class 
*                member functions.
*    
*                This class represents a collector, which stores pages of multipaged 
*                messages. Complete messages are assembled and returned back to 
*                the caller of CollectL() method.
*  
*                CCbsRecCollector stores message pages in a number of dynamic arrays.
*                If all but one page of a message are present in collector, and
*                the remaining page is received, the pages will be assembled and
*                the corresponding message chain deleted.
* 
*                The maximum number of incomplete messages stored in collector at once
*                is fixed and determined by KMaxCollectorMessages in CCbsRecCollector.cpp.
* 
*                CCbsRecCollector implements a circular list to contain message.
*                Each incomplete message occupies a slot in this list. If the list already
*                contains KMaxCollectorMessages messages, the next received multipaged
*                message will delete all received pages of the oldest message in list.
* 
*                On receival of a message page, the collector compares network information
*                (PLMN, LAC, CellId) of both messages to decide whether pages are of
*                the same message. In short, for pages to be of the same message
*                their network information have to meet the requirements set by the 
*                geographical scope of the already collected page.
*                See ETSI GSM 03.41 for a detailed description. 
*
*/


// INCLUDE FILES
#include "CbsServerPanic.h"
#include "CCbsRecCollector.h"
#include "CCbsRecMessage.h"
#include "CCbsMessageFactory.h"
#include "CCbsMessageCleanUpTimer.h"
#include "CbsLogger.h"

// CONSTANTS

// Maximum number of pages in a single CB message.
const TInt KMaxMessagePages = 15;

// Maximum number of partial messages contained in the collector list.
const TInt KMaxCollectorMessages = 10;

// ================= MEMBER FUNCTIONS =======================

// -----------------------------------------------------------------------------
// CCbsRecCollector::CCbsRecCollector
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CCbsRecCollector::CCbsRecCollector( CCbsMessageFactory& aFactory )
    : iRootNodeIterator( 0 ),
    iFactory( aFactory )
    {
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CCbsRecCollector::ConstructL()
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsRecCollector::ConstructL()");

    // initialize the root node array
    iRootNodeArray = new ( ELeave ) CArrayPtrFlat< CMessageBuffer >
        ( KMaxCollectorMessages );
    iRootNodeArray->SetReserveL( KMaxCollectorMessages );
    iMessageCleanupTimerArray = new ( ELeave ) CArrayPtrFlat< CCbsMessageCleanupTimer >
        ( KMaxCollectorMessages );

    for ( TInt i( 0 ); i < KMaxCollectorMessages; i++)
        {
        CMessageBuffer* array = new ( ELeave )
            CMessageBuffer( KMaxMessagePages );
        CleanupStack::PushL( array );
        iRootNodeArray->AppendL( array );

		CCbsMessageCleanupTimer* messageCleanUpTimer = CCbsMessageCleanupTimer::NewL( *this, *array );
		iMessageCleanupTimerArray->AppendL( messageCleanUpTimer );
        
        CleanupStack::Pop(); // array
        }
    CBSLOGSTRING("CBSSERVER: <<< CCbsRecCollector::ConstructL()");
    }


// -----------------------------------------------------------------------------
// CCbsRecCollector::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
 CCbsRecCollector* CCbsRecCollector::NewL( CCbsMessageFactory& aFactory )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsRecCollector::NewL()");

    CCbsRecCollector* self = new ( ELeave ) CCbsRecCollector( aFactory );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop();

    CBSLOGSTRING("CBSSERVER: <<< CCbsRecCollector::NewL()");
    return self;
    }
    
// Destructor
CCbsRecCollector::~CCbsRecCollector()
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsRecCollector::~CCbsRecCollector()");

    if ( iRootNodeArray )
        {
        TInt rootArrayLength( iRootNodeArray->Count() );
        for ( TInt i( 0 ); i < rootArrayLength; i++ )
            {
            iRootNodeArray->At( i )->ResetAndDestroy();
            }
        iRootNodeArray->ResetAndDestroy();
        delete iRootNodeArray;
        }

    if ( iMessageCleanupTimerArray )
        {
        iMessageCleanupTimerArray->ResetAndDestroy();
        delete iMessageCleanupTimerArray;
        }

    CBSLOGSTRING("CBSSERVER: <<< CCbsRecCollector::~CCbsRecCollector()");
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::CollectL
// Adds a message to the location pointed by iRootNodeIterator.
//   
// Checks if all pages of message are present. If all pages
// are present, sets aCompleted to ECbsMessageComplete,
// combines message pages into a single message, destroys
// pages from the list and returns the complete message
// to caller in aMessage.

// Algorithm:
//   1.  Check if other pages of this message exist in list.
//       (serial number and message identifier match)
//   2.  True: Check if the other messages in chain need
//       to be deleted. This is based on geographical scope
//       and network information.
//       Add this message to the correct message chain.
//       False: Add this message to the chain pointed
//       by the iRootNodeIterator.
//   3.  Seek out the position in the chosed message chain so that
//       the page number sequence remains ordered (1, 2, .., n)
//   4.  Add the page to the correct position in chain.
//   5.  Check if all pages of this message exist (number count).
//   6.  True: Combine message pages into a single message
//       and return this page to the caller. Set aCompleted
//       to ECbsMessageComplete.
//       False: Set aCompleted to ECbsMessageIncomplete.
//
// Note: Ownership of aMessage assumed. aMessage assumed to be
// on cleanup stack.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//  
CCbsMessage* CCbsRecCollector::CollectL( 
    CCbsMessage* aMessage, TInt aMessageType )  
    {
	CBSLOGSTRING("CBSSERVER: >>> CCbsRecCollector::CollectL()");

    // 1.
    CMessageBuffer* array = FindChainContainingPage( *aMessage );
    CCbsMessage* mergedMessage = NULL;

    if ( array  ) 
        {		
        CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): array != NULL");

        // 2. (True), 3.
        __ASSERT_DEBUG( array->Count() > 0, 
            CbsServerPanic( ECbsCollectorArrayEmpty ) );

        // Check geographical scope and network info to decide whether
        // existing pages in this chain should be deleted.
        TBool preserveExistingPages( 
            CheckPageAreaInfoMatch( *aMessage, *array->At( 0 ) ) );
		
        CBSLOGSTRING2("CBSSERVER: CCbsRecCollector::CollectL(): preserveExistingPages: %d", preserveExistingPages );

        if ( preserveExistingPages ) 
            {			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Adding msg to chain...");
            // aMessage is deleted, if it is a duplicate.
            AddMessageToChainL( aMessage, *array );			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Adding msg to chain OK.");
            }
        else 
            {			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Deleting chain...");
            DeleteChainL( *array );			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Deleting chain OK.");
            array->InsertL( 0, aMessage );			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Msg added to array.");
            }
        // aMessage, ownership transferred to msg chain or
        // aMessage has been deleted by AddMessageToChainL
        CleanupStack::Pop(); 

        // 5. Check if this chain contains all pages of the message.
        if ( AllPagesPresent( *array ) )
            {			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): All pages present, merging...");

            // 6. merge creates a new copy of this message
            // leaves a pointer to msg to cleanup stack
            mergedMessage = MergePagesLC( *array ); // on CS			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Merging OK.");

			// we stop timer if its livecast message
			if ( aMessageType == ECbsMessageLivecast )
				{				
                CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Msg type == ECbsMessageLivecast, stopping timer.");

				TKeyArrayFix key(0, ECmpTUint16);
				TInt index;
				iRootNodeArray->Find( array, key, index);
				iMessageCleanupTimerArray->At( index )->StopTimer();
                
                CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Timer stopped.");
				}

			CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Deleting chain...");
            DeleteChainL( *array );
			CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Deleting chain OK.");

            CleanupStack::Pop(); // mergedMessage
            }
        }
    else
        {		
        CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): array == NULL");

		// if message pagenumber does not start from 1, its not inserted to chain.
		if ( aMessage->ThisPage () == 1 ) 
			{			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): aMessage->ThisPage () == 1");

			// 2. (False)
			// add this page as the first node in chain pointed by 
			// iRootNodeIterator. Delete any pages contained in the chain
			// occuping this location first.
			array = iRootNodeArray->At( iRootNodeIterator );
			DeleteChainL( *array );
			array->InsertL( 0, aMessage );
			CleanupStack::Pop(); // aMessage, ownership transferred to msg chain.
			iRootNodeIterator++;
			// Return to the first message, if passed the
			// maximum messages.
			iRootNodeIterator %= KMaxCollectorMessages;

			if ( aMessageType == ECbsMessageLivecast )
				{				
                CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): aMessageType == ECbsMessageLivecast");

				// start timeout timer for livecast message
				TKeyArrayFix key(0, ECmpTUint16);
				TInt index;
				iRootNodeArray->Find( array, key, index);
				iMessageCleanupTimerArray->At( index )->StartTimer();
				
                CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): Timer started.");
				}
			}
		else
			{			
            CBSLOGSTRING("CBSSERVER: CCbsRecCollector::CollectL(): CleanupStack::Pop()");
			CleanupStack::Pop();
			}
        }
    // mergedMessage == NULL if msg not completed,
    // otherwise return the complete message
	CBSLOGSTRING("CBSSERVER: <<< CCbsRecCollector::CollectL(), returning mergedMessage.");

    return mergedMessage;
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::DeleteChainL
// Deletes all message pages contained in aArray.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//  
void CCbsRecCollector::DeleteChainL( 
    CMessageBuffer& aArray ) const
    {
    aArray.ResetAndDestroy();
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::AllPagesPresent
// Returns ETrue if all pages of the message of aArray are present.
// Counts pages in message chain aArray and compares the result
// against the total number of pages in the message.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
// 
TBool CCbsRecCollector::AllPagesPresent( 
    const CMessageBuffer& aArray ) const
    {
    TBool result( ETrue );
    if ( TUint( aArray.Count() ) < aArray.At( 0 )->TotalPages() )
        {
        result = EFalse;        
        }
    
    return result;
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::MergePagesLC
// Returns a complete message in aMessage.
// Merges all pages in message chain aArray and returns
// a pointer to the resulting assembled message. The pointer
// is also left on the cleanup stack.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
CCbsMessage* CCbsRecCollector::MergePagesLC( 
    CMessageBuffer& aArray ) const
    {
    if ( aArray.Count() <= 0 )
        {
        User::Leave( KErrNotFound );
        }
    // Create a new message based on first message page in the chain.
    CCbsMessage* message = iFactory.CreateMessageL( *aArray.At( 0 ) );

    CleanupStack::PushL( message ); // left on cleanup stack

    // Traverse through the chain and merge contents.
    TInt length( 0 );
    TInt count( aArray.Count() );

    // If this is a Livecast message, use the 8-bit representation
    // (message not decoded).
    if ( message->IsLivecastMessage() )
        {
        for ( TInt j( 0 ); j < count; j++ )
            {
            length += aArray.At( j )->Contents8().Length();
            }
        
        __ASSERT_DEBUG( length >= 0, CbsServerPanic( ECbsCollectorMergeFailed ) );

        message->ReserveContentSize8L( length );
        count = aArray.Count();
        
        for ( TInt i( 1 ); i < count; i++ ) 
            {
            message->AppendContent8( aArray.At( i )->Contents8() );
            }
        }
    // Else use the 16-bit representation (message already decoded)
    else
        {
        for ( TInt j( 0 ); j < count; j++ )
            {
            length += aArray.At( j )->Contents().Length();
            }
    
	    __ASSERT_DEBUG( length >= 0, CbsServerPanic( ECbsCollectorMergeFailed ) );

	    message->ReserveContentSizeL( length );
	    count = aArray.Count();

    // Append the rest of the pages (first page handled earlier)
	    for ( TInt i( 1 ); i < count; i++ ) 
	        {
	        message->AppendContent( aArray.At( i )->Contents() );
	        }
        }
    return message;
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::FindChainContainingPage
// Returns the buffer containing pages of same message.
// Finds and returns a message chain which already contains pages
// of aMessage's message. If none is found, NULL is returned.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
CMessageBuffer* CCbsRecCollector::FindChainContainingPage( 
    const CCbsMessage& aMessage ) const
    {
    TBool quitSeek( EFalse );
    TInt seekIterator( 0 );
    TCbsDbMessageKey key( aMessage.Key() );
    TCbsDbTopicNumber topicNumber( aMessage.TopicNumber() );
    CMessageBuffer* array = NULL;
    
    // find out if the root array contains pages of this message
    while ( ( seekIterator < KMaxCollectorMessages ) && !quitSeek  )
        {
        array = iRootNodeArray->At( seekIterator );
        if ( array->Count() > 0 ) 
            {
            CCbsMessage* msg = array->At( 0 );
            if ( key == msg->Key() && topicNumber == msg->TopicNumber() )
                {
                quitSeek = ETrue;
                }
            }
        seekIterator++;
        }

    if ( !quitSeek )
        {
        array = NULL;
        }
    
    return array;
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::AddMessageToChainL
// Adds message page aMessage to the correct position in message chain aArray
//        
// Message chains are ordered in ascending page number order.
// Duplicate pages are not accepted.
//
// Ownership of aMessage is transferred to aArray, if the given page
// hasn't been already collected. The given page will be deleted,
// if it already exists in the chain.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsRecCollector::AddMessageToChainL( 
    CCbsMessage* aMessage, 
    CMessageBuffer& aArray ) const
    {
    // Find out a position for this page in the chain.
    TInt chainLength( aArray.Count() );    
    TInt insertPosition( -1 );
    TBool duplicate( EFalse );

    for ( TInt i( 0 ); ( i < chainLength ) && !duplicate; i++ )
        {
        CCbsMessage* msg = aArray.At( i );
        if ( insertPosition == -1 && msg->ThisPage() > aMessage->ThisPage() )
            {
            insertPosition = i;
            }
        else if ( msg->ThisPage() == aMessage->ThisPage() )
            {
            duplicate = ETrue; // This page has been already collected
            delete aMessage;
            }
        }

    // If this message was not a duplicate, add it to the chain
    if ( !duplicate )
        {
        if ( insertPosition == -1 )
            {
            aArray.AppendL( aMessage );
            }
        else
            {
            aArray.InsertL( insertPosition, aMessage );
            }
        }    
    }

// -----------------------------------------------------------------------------
// CCbsRecCollector::CheckPageAreaInfoMatch
// Checks if these pages can be merged. Returns ETrue, if merging is acceptable.
//   
// Decision is based network information and geographical scope of 
// pages. Network information consists of cell id, location area code
// and operator id.
// 
// Assumption: aPage1 and aPage have identical message
// identifiers and serial numbers. 
// Returns ETrue if the pages are of same message.
// On EFalse previous pages should be deleted.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TBool CCbsRecCollector::CheckPageAreaInfoMatch( 
    const CCbsMessage& aPage1, 
    const CCbsMessage& aPage2 ) const
    {   
    RMobilePhone::TMobilePhoneNetworkInfoV1 info1;
    RMobilePhone::TMobilePhoneNetworkInfoV1 info2;

    aPage1.GetPLMN( info1 );
    aPage2.GetPLMN( info2 );

    TBool result( EFalse );

    if ( info1.iCountryCode == KRecMessageNoNetworkInfo || 
        info2.iCountryCode == KRecMessageNoNetworkInfo )
        {
        // Network info is unavailable; we must assume that the aPage1
        // is of the same page area as aPage2, so we return ETrue
        // unconditionally.
        result = ETrue;
        }

    if ( info1.iCountryCode == info2.iCountryCode && 
        info1.iNetworkId == info2.iNetworkId )
        // PLMN match
        {
        if ( aPage1.LAC() == aPage2.LAC() )
            // LAC match
            {
            if ( aPage1.CellId() == aPage2.CellId() )
                // Cell match
                {
                // Full network information match
                result = ETrue;
                }
            else
                {
                // Cell mismatch
                if ( aPage1.GeographicalScope() == 
                    ECbsRecGeographicalScopeCell )
                    // Cell id mismatch and scoped cell wide.
                    {
                    result = EFalse;
                    }
                else
                    // Cell id mismatch, not scoped cell wide.
                    {
                    result = ETrue;
                    }
                }
            }            
        else
            // LAC mismatch
            {
            if ( aPage1.GeographicalScope() == 
                ECbsRecGeographicalScopePLMN )
                {
                // LAC mismatch but scoped operator-wide
                result = ETrue;
                }
            else
                {
                // LAC mismatch and not scoped operator-wide
                result = EFalse;
                }
            }
        }
    else
        // PLMN mismatch
        {
        result = EFalse; // operator mismatch
        }

    return result;    
    }


// ================= OTHER EXPORTED FUNCTIONS ==============

//  End of File