/*++

   Copyright    (c)    1997    Microsoft Corporation

   Module  Name :
      abw.hxx

   Abstract:
      Declares constants, types and functions for bandwidth throttling.

   Author:

      Bilal Alam             ( t-bilala )   13-March-1997

   Environment:

      User Mode -- Win32

   Project:

      Internet Services Asynchronous Thread Queue Library

   Revision History:

--*/

typedef struct _BANDWIDTH_LEVELS {
    DWORD dwSpecifiedLevel;
    DWORD dwLowThreshold;  // uses 0.90 * bwSpecifiedLevel
    DWORD dwHighThreshold; // uses 1.10 * bwSpecifiedLevel
} BANDWIDTH_LEVELS;

# define ATQ_LOW_BAND_THRESHOLD(bw)   (bw.dwLowThreshold)
# define ATQ_HIGH_BAND_THRESHOLD(bw)  (bw.dwHighThreshold)

# define CIRCULAR_INCREMENT( sm_pB, sm_rgB, size)  \
            (sm_pB) = ((((sm_pB) + 1) < (sm_rgB)+(size)) ? (sm_pB)+1 : (sm_rgB))


typedef enum {

    ZoneLevelLow = 0,      // if MeasuredBw < Bandwidth specified
    ZoneLevelMedium,       // if MeasuredBw approxEquals Bandwidth specified
    ZoneLevelHigh,         // if MeasuredBw > Bandwidth specified
    ZoneLevelMax           // just a boundary element
} ZoneLevel;

/*++
  The following array specifies the status of operations for different
  operations in different zones of the measured bandwidth.

  We split the range of bandwidth into three zones as specified in
   the type ZoneLevel. Depending upon the zone of operation, differet
   operations are enabled/disabled. Priority is given to minimize the amount
   of CPU work that needs to be redone when an operation is to be rejected.
   We block operations on which considerable amount of CPU is spent earlier.
--*/

static OPERATION_STATUS sm_rgStatus[ZoneLevelMax][AtqIoMaxOp] = {

    // For ZoneLevelLow:          Allow All operations
    { StatusRejectOperation,
      StatusAllowOperation,
      StatusAllowOperation,
      StatusAllowOperation,
      StatusAllowOperation,
      StatusAllowOperation,
    },

    // For ZoneLevelMedium:
    { StatusRejectOperation,
      StatusBlockOperation,      // Block Read
      StatusAllowOperation,
      StatusAllowOperation,
      StatusAllowOperation,
      StatusAllowOperation
    },

    // For ZoneLevelHigh
    { StatusRejectOperation,
      StatusRejectOperation,    // Reject Read
      StatusAllowOperation,     // Allow Writes
      StatusBlockOperation,     // Block TransmitFile
      StatusBlockOperation,     // Block TransmitFileAndRecv
      StatusAllowOperation      // Allow SendAndRecv
    }
};

#define ATQ_BW_INFO_SIGNATURE        ( (DWORD) 'BQTA')
#define ATQ_BW_INFO_SIGNATURE_FREE   ( (DWORD) 'BQTX')
#define ATQ_BW_INFO_MAX_DESC         32

/*++
  class BANDWIDTH_INFO

  This class encapsulates all the statistics and state used in the
  implementation of bandwidth throttling.
--*/
class BANDWIDTH_INFO
{
private:

    DWORD                   _Signature;
    LONG                    _cReference;
    BOOL                    _fIsFreed;
    BOOL                    _fPersistent;

    // statistics

    DWORD                   _cCurrentBlockedRequests;
    DWORD                   _cTotalBlockedRequests;
    DWORD                   _cTotalAllowedRequests;
    DWORD                   _cTotalRejectedRequests;
    DWORD                   _dwMeasuredBw;

    // bandwidth status members

    BANDWIDTH_LEVELS        _bandwidth;
    LARGE_INTEGER           _rgBytesXfered[ ATQ_HISTOGRAM_SIZE ];
    LARGE_INTEGER *         _pBytesXferCur;
    LARGE_INTEGER           _cbXfered;
    OPERATION_STATUS *      _pStatus;
    BOOL                    _fEnabled;

    // list of blocked context manipulation

    CRITICAL_SECTION        _csPrivateLock;
    LIST_ENTRY              _BlockedListHead;
    DWORD                   _cMaxBlockedList;

    // user friendly description of this descriptor.  Useful for debugging

#if DBG
    CHAR                    _achDescription[ ATQ_BW_INFO_MAX_DESC ];
#endif

    // private members shared by all BANDWIDTH_INFO objects

    static CRITICAL_SECTION         sm_csSharedLock;
    static LIST_ENTRY               sm_BornListHead;
    static LIST_ENTRY               sm_ActiveListHead;
    static DWORD                    sm_cBornList;
    static DWORD                    sm_cActiveList;
    static ALLOC_CACHE_HANDLER *    sm_pachBWInfos;
    static BOOL                     sm_fGlobalEnabled;
    static BOOL                     sm_fGlobalActive;
    static DWORD                    sm_cNonInfinite;

    // private member functions

    VOID Initialize( IN BOOL fPersistent );
    VOID Terminate( VOID );
    BOOL UnblockRequest( IN OUT PATQ_CONT pAtqContext );
    BOOL UpdateBandwidth( VOID );

    VOID Lock( VOID )
    {
        EnterCriticalSection( &_csPrivateLock );
    }

    VOID Unlock( VOID )
    {
        LeaveCriticalSection( &_csPrivateLock );
    }

    static VOID SharedLock( VOID )
    {
        EnterCriticalSection( &sm_csSharedLock );
    }

    static VOID SharedUnlock( VOID )
    {
        LeaveCriticalSection( &sm_csSharedLock );
    }

    VOID RemoveFromActiveList( VOID )
    // Assume caller has acquired shared lock!
    {
        sm_cActiveList--;
        if ( !sm_cActiveList )
        {
            sm_fGlobalActive = FALSE;
        }
#ifndef _NO_TRACING_
        CHKINFO(( DBG_CONTEXT,
                     "Removed %p (%s) from active bandwidth info list (list size = %d)\n",
                     this,
                     _achDescription,
                     sm_cActiveList ));
#else
        ATQ_PRINTF(( DBG_CONTEXT,
                     "Removed %p (%s) from active bandwidth info list (list size = %d)\n",
                     this,
                     _achDescription,
                     sm_cActiveList ));
#endif
        RemoveEntryList( &_ActiveListEntry );
        _fMemberOfActiveList = FALSE;
        _ActiveListEntry.Flink = NULL;
    }

    VOID RemoveFromBornList( VOID )
    {
        SharedLock();

        sm_cBornList--;
#ifndef _NO_TRACING_
        CHKINFO(( DBG_CONTEXT,
                     "Removed %p (%s) from born bandwidth info list (list size = %d)\n",
                     this,
                     _achDescription,
                     sm_cBornList ));
#else
        ATQ_PRINTF(( DBG_CONTEXT,
                     "Removed %p (%s) from born bandwidth info list (list size = %d)\n",
                     this,
                     _achDescription,
                     sm_cBornList ));
#endif
        RemoveEntryList( &_BornListEntry );
        _BornListEntry.Flink = NULL;

        // Now remove from the active list

        if ( _fMemberOfActiveList )
        {
            RemoveFromActiveList();
        }

        SharedUnlock();
    }

public:

    LIST_ENTRY              _BornListEntry;
    LIST_ENTRY              _ActiveListEntry;
    BOOL                    _fMemberOfActiveList;

    static DWORD            sm_cSamplesForTimeout;

    // public member functions

    VOID Reference( VOID )
    {
        InterlockedIncrement( &_cReference );
    }

    VOID Dereference( VOID )
    {
        if ( !_fPersistent && !InterlockedDecrement( &_cReference ) )
        {
            delete this;
        }
    }

    OPERATION_STATUS QueryStatus( ATQ_OPERATION operation ) const
    {
        return _pStatus[ operation ];
    }

    BOOL IsFreed( VOID )
    {
        return _fIsFreed;
    }

    VOID IncTotalBlockedRequests( VOID )
    {
        INC_ATQ_COUNTER( _cTotalBlockedRequests );
    }

    DWORD QueryTotalBlockedRequests( VOID ) const
    {
        return _cTotalBlockedRequests;
    }

    VOID IncTotalAllowedRequests( VOID )
    {
        INC_ATQ_COUNTER( _cTotalAllowedRequests );
    }

    DWORD QueryTotalAllowedRequests( VOID ) const
    {
        return _cTotalAllowedRequests;
    }

    VOID IncTotalRejectedRequests( VOID )
    {
        INC_ATQ_COUNTER( _cTotalRejectedRequests );
    }

    DWORD QueryTotalRejectedRequests( VOID ) const
    {
        return _cTotalRejectedRequests;
    }

    VOID IncCurrentBlockedRequests( VOID )
    {
        INC_ATQ_COUNTER( _cCurrentBlockedRequests );
    }

    DWORD QueryCurrentBlockedRequests( VOID ) const
    {
        return _cCurrentBlockedRequests;
    }

    VOID DecCurrentBlockedRequests( VOID )
    {
        DEC_ATQ_COUNTER( _cCurrentBlockedRequests );
    }

    DWORD QueryMeasuredBw( VOID ) const
    {
        return _dwMeasuredBw;
    }

    BOOL Enabled( VOID ) const
    {
        return _fEnabled;
    }

    DWORD QuerySignature( VOID ) const
    {
        return _Signature;
    }

    DWORD QueryMaxBlockedSize( VOID ) const
    {
        return _cMaxBlockedList;
    }

    VOID SetMaxBlockedSize( DWORD Size )
    {
        _cMaxBlockedList = Size;
    }

    VOID AddToActiveList( VOID )
    {
        if ( _fEnabled && !_fMemberOfActiveList )
        {
            SharedLock();
            if ( _fEnabled && !_fMemberOfActiveList )
            {
                sm_cActiveList++;

#ifndef _NO_TRACING_
                CHKINFO(( DBG_CONTEXT,
                             "Added %p (%s) to active bandwidth info list (list size = %d)\n",
                             this,
                             _achDescription,
                             sm_cActiveList ));
#else
                ATQ_PRINTF(( DBG_CONTEXT,
                             "Added %p (%s) to active bandwidth info list (list size = %d)\n",
                             this,
                             _achDescription,
                             sm_cActiveList ));
#endif

                InsertTailList( &sm_ActiveListHead, &_ActiveListEntry );

                _fMemberOfActiveList = TRUE;

                sm_fGlobalActive = TRUE;
            }
            SharedUnlock();
        }
    }

    VOID AddToBornList( VOID )
    {
        SharedLock();

        sm_cBornList++;

        InsertTailList( &sm_BornListHead, &_BornListEntry );

        SharedUnlock();
    }

    BOOL UpdateBytesXfered( IN PATQ_CONT pAtqContext,
                            IN DWORD cbIo )
    {
        if ( _fEnabled )
        {
            Lock();

            _pBytesXferCur->QuadPart = _pBytesXferCur->QuadPart + cbIo;

            Unlock();
        }
        return TRUE;
    }

    CHAR * QueryDescription( VOID ) const
    {
        // :(
#if DBG
        return (CHAR*) _achDescription;
#else
        return NULL;
#endif
    }

    VOID SetDescription( CHAR * pszDescription )
    {
#if DBG
        Lock();
        strncpy( _achDescription,
                 pszDescription ? pszDescription : "Unknown",
                 sizeof( _achDescription ) );
        Unlock();
#endif
    }

    BANDWIDTH_INFO( IN BOOL fPersistent )
    {
        Initialize( fPersistent );
    }

    ~BANDWIDTH_INFO( VOID )
    {
        Terminate();
    }

    BOOL PrepareToFree( VOID );
    BOOL RemoveFromBlockedList( IN PATQ_CONT patqContext );
    BOOL BlockRequest( IN OUT PATQ_CONT patqContext );
    DWORD SetBandwidthLevel( IN DWORD Data );
    BOOL CheckAndUnblockRequests( VOID );

    DWORD SetMaxBlockedListSize( IN DWORD Data );
    DWORD QueryBandwidthLevel( VOID );
    BOOL ClearStatistics( VOID );
    BOOL GetStatistics( OUT ATQ_STATISTICS * patqStats );

    static BOOL GlobalActive( VOID )
    {
        return sm_fGlobalActive;
    }

    static BOOL GlobalEnabled( VOID )
    {
        return sm_fGlobalEnabled;
    }

    static BOOL AbwInitialize( VOID );
    static BOOL AbwTerminate( VOID );
    static BOOL UpdateAllBandwidths( VOID );

    void * operator new( size_t s )
    {
        ATQ_ASSERT( s == sizeof( BANDWIDTH_INFO ));

        // allocate from allocation cache.
        ATQ_ASSERT( sm_pachBWInfos != NULL );
        return (sm_pachBWInfos->Alloc());
    }

    void operator delete( void * pBandwidthInfo )
    {
        ATQ_ASSERT( pBandwidthInfo != NULL );

        // free to the allocation pool
        ATQ_ASSERT( NULL != sm_pachBWInfos );
        ATQ_REQUIRE( sm_pachBWInfos->Free( pBandwidthInfo ) );

        return;
    }
};

typedef BANDWIDTH_INFO * PBANDWIDTH_INFO;
