/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1996 Microsoft Corporation. All Rights Reserved.

Component: Session Manager

File: Sessmgr.h

Owner: PramodD

This is the session manager header file.
===================================================================*/
#ifndef SESSMGR_H
#define SESSMGR_H

#include "debug.h"
#include "idhash.h"
#include "idgener.h"
#include "compcol.h"
#include "request.h"
#include "response.h"
#include "server.h"
#include "viperint.h"
#include "ftm.h"
#include "memcls.h"

/*===================================================================
  #defines
===================================================================*/

// Min/Max session timeout in minutes
#define SESSION_TIMEOUT_MIN		        1		// 1 minute
#define SESSION_TIMEOUT_MAX		        1440	// 1 day

// Master hash table sizes
#define SESSION_MASTERHASH_SIZE1_MAX    499
#define SESSION_MASTERHASH_SIZE2_MAX    31
#define SESSION_MASTERHASH_SIZE3_MAX    13

// Timeout bucket hash tables sizes
#define SESSION_TIMEOUTHASH_SIZE1_MAX   97
#define SESSION_TIMEOUTHASH_SIZE2_MAX   29
#define SESSION_TIMEOUTHASH_SIZE3_MAX   11

// Min/Max # of timeout buckets (hash tables)
#define SESSION_TIMEOUTBUCKETS_MIN      10
#define SESSION_TIMEOUTBUCKETS_MAX      45

// max value of GetTickCount()
#define	DWT_MAX 0xFFFFFFFF

// session killer workitem default wait
#define MSEC_ONE_MINUTE     60000   // 1 min

#include "asptlb.h"

/*===================================================================
  Forward declarations
===================================================================*/

class CAppln;
class CHitObj;
class CSession;

/*===================================================================
  C S e s s i o n V a r i a n t s
===================================================================*/
class CSessionVariants : public IVariantDictionary
	{
private:
    ULONG               m_cRefs;            // ref count
	CSession *			m_pSession;			// pointer to parent object
	CompType            m_ctColType;        // collection type
	CSupportErrorInfo	m_ISupportErrImp;	// implementation of ISupportErr

	HRESULT ObjectNameFromVariant(VARIANT &vKey, WCHAR **ppwszName, 
	                              BOOL fVerify = FALSE);

public:
	CSessionVariants();
	~CSessionVariants();

	HRESULT Init(CSession *pSession, CompType ctColType);
	HRESULT UnInit();

	// The Big Three

	STDMETHODIMP		 QueryInterface(const GUID &, void **);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	// OLE Automation Interface

	STDMETHODIMP get_Item(VARIANT VarKey, VARIANT *pvar);
	STDMETHODIMP put_Item(VARIANT VarKey, VARIANT var);
	STDMETHODIMP putref_Item(VARIANT VarKey, VARIANT var);
	STDMETHODIMP get_Key(VARIANT VarKey, VARIANT *pvar);
	STDMETHODIMP get__NewEnum(IUnknown **ppEnumReturn);
	STDMETHODIMP get_Count(int *pcValues);
	STDMETHODIMP Remove(VARIANT VarKey);
	STDMETHODIMP RemoveAll();
	
    // Cache on per-class basis
    ACACHE_INCLASS_DEFINITIONS()
	};

/*===================================================================
  C  S e s s i o n  I D
===================================================================*/
struct CSessionId
    {
	DWORD m_dwId;	// Session Id
	DWORD m_dwR1;	// Session Id random element 1
	DWORD m_dwR2;	// Session Id random element 2

    CSessionId(DWORD dwId = INVALID_ID, DWORD dwR1 = 0, DWORD dwR2 = 0);
    };

inline CSessionId::CSessionId(DWORD dwId, DWORD dwR1, DWORD dwR2)
    {
    m_dwId = dwId;
    m_dwR1 = dwR1;
    m_dwR2 = dwR2;
    }

/*===================================================================
  C  S e s s i o n
===================================================================*/
class CSession : public ISessionObjectImpl, public CFTMImplementation
    {

friend class CSessionMgr;
friend class CSessionVariants;

private:

    //========= Misc flags
    
	DWORD m_fInited : 1;		  // Are we initialized?
	DWORD m_fLightWeight : 1;     // Is in lightweight form?
	DWORD m_fOnStartFailed : 1;	  // Session_OnStart failed?
	DWORD m_fOnStartInvoked : 1;  // Session_OnStart invoked?
	DWORD m_fOnEndPresent : 1;    // Need to invoke Session_OnEnd ?
	DWORD m_fTimedOut : 1;        // Session timed out?
	DWORD m_fStateAcquired : 1;   // Any property set (!m_fCanDelete)?
	DWORD m_fCustomTimeout : 1;   // Timeout different from standard?
	DWORD m_fAbandoned : 1;       // Session abandoned?
	DWORD m_fTombstone : 1;       // ASP is done with the session?
	DWORD m_fInTOBucket : 1;      // Session in a timeout bucket?
	DWORD m_fSessCompCol : 1;     // Component collection present?
	DWORD m_fSecureSession : 1;   //  Is the session used over a secure line?
    DWORD m_fCodePageSet : 1;     // CodePage explicitly set?
    DWORD m_fLCIDSet     : 1;     // LCID explicitly set?

	//========= Pointers to related objects
	
	CAppln  *m_pAppln;    // Session's Application
	CHitObj *m_pHitObj;   // Session's current HitObj
	
	//========= Session's dictionaries for presenting component collection
	
	CSessionVariants *m_pTaggedObjects;
	CSessionVariants *m_pProperties;

    //========= Session data

    CSessionId m_Id;        // Session ID + 2 random keys
    DWORD m_dwExternId;     // Session ID to be given out (Session.ID)

    DWORD m_cRefs;          // Ref count
	DWORD m_cRequests;      // Requests count

	// Timeout when current time (in minutes) reaches this
	// The timeout bucket is current_time mod #_of_buckets
	DWORD m_dwmTimeoutTime;
	
	long  m_lCodePage;	    // Code page for this session
	LCID  m_lcid;			// LCID for this session
	long  m_nTimeout;       // Current time value in minutes

	// to make session elem in the timeout bucket
	CObjectListElem m_TOBucketElem;
	
#ifndef PERF_DISABLE
    DWORD m_dwtInitTimestamp; // Timestamp of session creation for PERFMON
#endif

	//========= Session's Component Collection

	// to avoid the memory fragmentation component collection is
	// aggregated here. its validity is indicated by m_fSessCompCol flag
	CComponentCollection m_SessCompCol;  // Session scope objects
	
	//========= Viper Activity of this Session
	
	CViperActivity m_Activity;

	//========= Intrinsics for this Session
	
	CRequest    m_Request;
	CResponse   m_Response;
	CServer     m_Server;

	//========= SupportErrorInfo
	
	// Interface to indicate that we support ErrorInfo reporting
    CSupportErrorInfo m_ISuppErrImp;

public:	
	CSession();
	~CSession();

	HRESULT Init(CAppln *pAppln, const CSessionId &Id);

    // Convert to tombstone state
    HRESULT UnInit();

	// Convert to 'light-weight' state if possible
	HRESULT MakeLightWeight();

	// Create/Remove Session's component collection
	HRESULT CreateComponentCollection();
	HRESULT RemoveComponentCollection();

    // Check if the session should be deleted
    BOOL FShouldBeDeletedNow(BOOL fAtEndOfRequest);

	// Non-delegating object IUnknown
	STDMETHODIMP		 QueryInterface(REFIID, void **);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

    // Tombstone stub
	HRESULT CheckForTombstone();

	// ISessionObject functions
	STDMETHODIMP get_SessionID(BSTR *pbstrRet);
	STDMETHODIMP get_Timeout(long *plVar);
	STDMETHODIMP put_Timeout(long lVar);
	STDMETHODIMP get_CodePage(long *plVar);
	STDMETHODIMP put_CodePage(long lVar);
	STDMETHODIMP get_Value(BSTR bstr, VARIANT FAR * pvar);
	STDMETHODIMP put_Value(BSTR bstr, VARIANT var);
	STDMETHODIMP putref_Value(BSTR bstr, VARIANT var);
	STDMETHODIMP Abandon();
	STDMETHODIMP get_LCID(long *plVar);
	STDMETHODIMP put_LCID(long lVar);
	STDMETHODIMP get_StaticObjects(IVariantDictionary **ppDictReturn);
	STDMETHODIMP get_Contents(IVariantDictionary **ppDictReturn);

	// inline methods to access member properties
	CAppln                *PAppln();
	CHitObj               *PHitObj();
	CComponentCollection  *PCompCol();
	CViperActivity        *PActivity();
	CRequest              *PRequest();
	CResponse             *PResponse();
	CServer               *PServer();
	BOOL                   FCustomTimeout();
	BOOL                   FAbandoned();
	DWORD                  GetId();
	BOOL                   FInTOBucket();
	LCID				   GetLCID();
    long                   GetCodePage();
	DWORD                  GetTimeoutTime();
	BOOL                   FSecureSession(); 


    // inline methods to set member properties
	void    SetHitObj(CHitObj *pHitObj);
	void    SetOnStartFailedFlag();
	void    SetOnStartInvokedFlag();
	void    SetOnEndPresentFlag();
	HRESULT SetLCID(LCID lcid);
	
    // Misc inline methods
	DWORD   IncrementRequestsCount();
	DWORD   DecrementRequestsCount();
    DWORD   GetRequestsCount();
    BOOL    FCanDeleteWithoutExec();
    BOOL    FHasObjects();
	BOOL    FPassesIdSecurityCheck(DWORD dwR1, DWORD dwR2);
    void    AssignNewId(const CSessionId &Id);
    void    SetSecureSession(BOOL fSecure); 
    BOOL    FCodePageSet();
    BOOL    FLCIDSet();

    // AssertValid()
public:
#ifdef DBG
	virtual void AssertValid() const;
#else
	virtual void AssertValid() const {}
#endif

    // Cache on per-class basis
    ACACHE_INCLASS_DEFINITIONS()

	// Trace Log info -- keep in both free & checked builds so that ntsd extension will work for both builds
	// for FREE build, trace log is always NULL.  Checked builds, it must be enabled.
	static PTRACE_LOG gm_pTraceLog;
    };

/*===================================================================
  C  S e s s i o n   inlines
===================================================================*/

inline CAppln *CSession::PAppln()
    {
    Assert(m_fInited);
    return m_pAppln;
    }
    
inline CHitObj *CSession::PHitObj()
    {
    Assert(m_fInited);
    return m_pHitObj;
    }
    
inline CComponentCollection  *CSession::PCompCol()
    {
    Assert(m_fInited);
    return (m_fSessCompCol ? &m_SessCompCol : NULL);
    }
    
inline CViperActivity *CSession::PActivity()
    {
    Assert(m_fInited);
    return &m_Activity;
    }

inline CRequest *CSession::PRequest()
    {
    Assert(m_fInited);
    return &m_Request;
    }
    
inline CResponse *CSession::PResponse()
    {
    Assert(m_fInited);
    return &m_Response;
    }
    
inline CServer *CSession::PServer()
    {
    Assert(m_fInited);
    return &m_Server;
    }

inline BOOL CSession::FCustomTimeout()
    {
    Assert(m_fInited);
    return m_fCustomTimeout;
    }
    
inline BOOL CSession::FAbandoned()
    {
    Assert(m_fInited);
    return m_fAbandoned;
    }
    
inline DWORD CSession::GetId()
    {
    Assert(m_fInited);
    return m_Id.m_dwId;
    }

inline BOOL CSession::FInTOBucket()
    {
    Assert(m_fInited);
    return m_fInTOBucket;
    }

inline LCID CSession::GetLCID()
    {
    Assert(m_fInited);
    return (UINT)m_lcid;
    }

inline long CSession::GetCodePage()
{
    Assert(m_fInited);
    return m_lCodePage == 0 ? GetACP() : m_lCodePage;
}

inline BOOL CSession::FCodePageSet()
{
    Assert(m_fInited);
    return (m_fCodePageSet);
}

inline BOOL CSession::FLCIDSet()
{
    Assert(m_fInited);
    return (m_fLCIDSet);
}

inline DWORD CSession::GetTimeoutTime()
    {
    Assert(m_fInited);
    return m_dwmTimeoutTime;
    }


inline BOOL CSession::FSecureSession()
    {
    Assert(m_fInited);
    return m_fSecureSession;
    }

inline void CSession::SetHitObj(CHitObj *pHitObj)
    {
    Assert(m_fInited);
    Assert(pHitObj ? (m_pHitObj == NULL) : (m_pHitObj != NULL));
    m_pHitObj = pHitObj;
    }
    
inline void CSession::SetOnStartFailedFlag()
    {
    Assert(m_fInited);
    m_fOnStartFailed = TRUE;
    }
    
inline void CSession::SetOnStartInvokedFlag()
    {
    Assert(m_fInited);
    m_fOnStartInvoked = TRUE;
    }

inline void CSession::SetOnEndPresentFlag()
    {
    Assert(m_fInited);
    m_fOnEndPresent = TRUE;
    }
    
inline HRESULT CSession::SetLCID(LCID lcid)
    {
    Assert(m_fInited);
	if ((LOCALE_SYSTEM_DEFAULT == lcid) || IsValidLocale(lcid, LCID_INSTALLED))
	    {
	    m_lcid = lcid; 
	    return S_OK;
	    }
	 else
	    {
	    return E_FAIL;
	    }
    }

inline DWORD CSession::IncrementRequestsCount()
    {
    Assert(m_fInited);
    return InterlockedIncrement((LPLONG)&m_cRequests);
    }
    
inline DWORD CSession::DecrementRequestsCount()
    {
    Assert(m_fInited);
    return InterlockedDecrement((LPLONG)&m_cRequests);
    }
    
inline DWORD CSession::GetRequestsCount()
    {
    Assert(m_fInited);
    return m_cRequests;
    }
    
inline BOOL CSession::FCanDeleteWithoutExec()
    {
    // Return TRUE to delete CSession right away or FALSE to
    // post Viper request to execute Session_OnEnd()
	return (m_fOnStartFailed || !m_fOnEndPresent);
	}

inline BOOL CSession::FHasObjects()
    {
    return m_fSessCompCol && m_SessCompCol.FHasObjects();
    }
    
inline BOOL CSession::FPassesIdSecurityCheck(DWORD dwR1, DWORD dwR2)
    {
    Assert(m_fInited);
    return (m_Id.m_dwR1 == dwR1 && m_Id.m_dwR2 == dwR2);
    }

inline void CSession::AssignNewId(const CSessionId &Id)
    {
    Assert(m_fInited);
	m_Id = Id;
    }


inline void CSession::SetSecureSession(BOOL fSecure)
    {
    Assert(m_fInited);
    m_fSecureSession = fSecure;
    }

/*===================================================================
  C  S e s s i o n  M g r
===================================================================*/

class CSessionMgr
    {
private:
    // Flags
	DWORD m_fInited : 1;	            // Are we initialized?

    // Application
	CAppln *m_pAppln;

	// Sessions master hash table
	CIdHashTableWithLock m_htidMaster;

	// Number of posted Session Cleanup requests
	DWORD m_cSessionCleanupRequests;

	// Timeout buckets
	DWORD m_cTimeoutBuckets;
	CObjectListWithLock *m_rgolTOBuckets;

    // Session killer scheduler workitem
    DWORD m_idSessionKiller;    // workitem id

    DWORD m_dwmCurrentTime; // current time in minutes since start
    DWORD m_dwtNextSessionKillerTime;  // next session killer time

public:
	CSessionMgr();
	~CSessionMgr();

    // Init/Unit
	HRESULT	Init(CAppln *pAppln);
	HRESULT	UnInit();

    // Add/remove session killer workitem
    HRESULT ScheduleSessionKiller();
    HRESULT UnScheduleSessionKiller();
    BOOL    FIsSessionKillerScheduled();

    // Lock/Unlock master hash table
	HRESULT LockMaster();
    HRESULT UnLockMaster();
    
    // Lock/Unlock a timeout bucket hash table
	HRESULT LockTOBucket(DWORD iBucket);
    HRESULT UnLockTOBucket(DWORD iBucket);

    // Get current time in minute ticks
    DWORD GetCurrentTime();
    // Set the time when the session should be gone
    HRESULT UpdateSessionTimeoutTime(CSession *pSession);
    // Calculate which timeout bucket the session's in
    DWORD GetSessionTOBucket(CSession *pSession);

    // Generate new ID and cookie
    HRESULT GenerateIdAndCookie(CSessionId *pId, char *pszCookie);

    // Create new session object
    HRESULT NewSession(const CSessionId &Id, CSession **ppSession);

    // Reassign session's Id (reinsert session into master hash)
    HRESULT ChangeSessionId(CSession *pSession, const CSessionId &Id);
   
    // Master hash table manipulations
    HRESULT AddToMasterHash(CSession *pSession);
    HRESULT RemoveFromMasterHash(CSession *pSession);
    HRESULT FindInMasterHash(const CSessionId &Id, CSession **ppSession);

    // Insert/remove session into the timeout bucket hash table
    HRESULT AddSessionToTOBucket(CSession *pSession);
    HRESULT RemoveSessionFromTOBucket(CSession *pSession, BOOL fLock = TRUE);

    // Delete session now or queue for deletion
    HRESULT DeleteSession(CSession *pSession, BOOL fInSessActivity = FALSE);

    // Delete expired sessions from a given bucket
    HRESULT DeleteExpiredSessions(DWORD iBucket);

    // Delete all sessions (application shut-down code)
    HRESULT DeleteAllSessions(BOOL fForce);
    // Static iterator call back to delete all sessions
    static IteratorCallbackCode DeleteAllSessionsCB(void *, void *, void *);

    // The Session Killer 
    static VOID WINAPI SessionKillerSchedulerCallback(VOID *pv);

    // Incr/Decr/Get number of posted Session Cleanup requests
    void  IncrementSessionCleanupRequestCount();
    void  DecrementSessionCleanupRequestCount();
    DWORD GetNumSessionCleanupRequests();

    // AssertValid()
public:
#ifdef DBG
	virtual void AssertValid() const;
#else
	virtual void AssertValid() const {}
#endif
	};

inline BOOL CSessionMgr::FIsSessionKillerScheduled()
    {
    return (m_idSessionKiller != 0);
    }
    
inline HRESULT CSessionMgr::LockMaster()
    {
    m_htidMaster.Lock();
    return S_OK;
    }
    
inline HRESULT CSessionMgr::UnLockMaster()
    {
    m_htidMaster.UnLock();
    return S_OK;
    }
    
inline HRESULT CSessionMgr::LockTOBucket(DWORD iBucket)
    {
    Assert(m_rgolTOBuckets);
    Assert(iBucket < m_cTimeoutBuckets);
    m_rgolTOBuckets[iBucket].Lock();
    return S_OK;
    }
    
inline HRESULT CSessionMgr::UnLockTOBucket(DWORD iBucket)
    {
    Assert(m_rgolTOBuckets);
    Assert(iBucket < m_cTimeoutBuckets);
    m_rgolTOBuckets[iBucket].UnLock();
    return S_OK;
    }

inline DWORD CSessionMgr::GetCurrentTime()
    {
    return m_dwmCurrentTime;
    }

inline HRESULT CSessionMgr::UpdateSessionTimeoutTime(CSession *pSession)
    {
    Assert(pSession);

    // remember when the session times out
    pSession->m_dwmTimeoutTime =
        m_dwmCurrentTime + pSession->m_nTimeout + 1;

    return S_OK;    
    }

inline DWORD CSessionMgr::GetSessionTOBucket(CSession *pSession)
    {
    Assert(pSession->m_fInited);
    return (pSession->m_dwmTimeoutTime % m_cTimeoutBuckets);
    }

inline HRESULT CSessionMgr::AddToMasterHash(CSession *pSession)
    {
    Assert(m_fInited);
 	return m_htidMaster.AddObject(pSession->GetId(), pSession);
    }
    
inline HRESULT CSessionMgr::RemoveFromMasterHash(CSession *pSession)
    {
    Assert(m_fInited);
	return m_htidMaster.RemoveObject(pSession->GetId());
    }

inline void CSessionMgr::IncrementSessionCleanupRequestCount()
    {
    InterlockedIncrement((LPLONG)&m_cSessionCleanupRequests);
    }
    
inline void CSessionMgr::DecrementSessionCleanupRequestCount()
    {
    InterlockedDecrement((LPLONG)&m_cSessionCleanupRequests);
    }

inline DWORD CSessionMgr::GetNumSessionCleanupRequests()
    {
    return m_cSessionCleanupRequests;
    }

/*===================================================================
  G l o b a l s
===================================================================*/

// There are multiple session managers (one per application)
// The following variables are 1 per ASP.DLL

extern unsigned long g_nSessions;
extern CIdGenerator  g_SessionIdGenerator;
extern CIdGenerator  g_ExposedSessionIdGenerator;     

#endif // SESSMGR_H