/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    servinfo.hxx

Abstract:

    Contains the CServerInfo class declaration

Author:

    Richard L Firth (rfirth) 02-Oct-1996

Notes:

    In this implementation, we maintain a single host name per server

Revision History:

    02-Oct-1996 rfirth
        Created

--*/

//
// manifests
//

#define SERVER_INFO_SIGNATURE   'fIvS'

//
// values for PurgeKeepAlives dwForce parameter, method & function
//

#define PKA_NO_FORCE    0
#define PKA_NOW         1
#define PKA_AUTH_FAILED 2

//
// data (forward references)
//

extern const DWORD GlobalServerInfoTimeout;

//
// forward references
//

class CFsm_GetConnection;
class HTTP_REQUEST_HANDLE_OBJECT;

//
// classes
//

//
// CServerInfo - we maintain an aged list of CServerInfo's. These are used as a
// database of all useful information about a server
//

class CServerInfo {

private:

    //
    // m_ServerInfoList, m_List - CServerInfo's are kept in a list
    //

    LIST_ENTRY m_List;

    SERIALIZED_LIST * m_ServerInfoList;

    //
    // m_Expires - system tick count at which this entry will expire
    //

    LONG m_Expires;
    LONG m_Wrap;
    
    //
    // m_ReferenceCount - number of other structures referencing this
    //

    LONG m_ReferenceCount;

    //
    // m_HostName - primary host name of the server. Stored as LOWER CASE so
    // that we don't have to perform case insensitive string comparisons each
    // time
    //

    ICSTRING m_HostName;

    //
    // m_Hash - hash value of m_HostName. Check this value first
    //

    DWORD m_Hash;

    //
    // m_ProxyLink - if this is an origin server, we link to the proxy
    //   that may carry
    //

    CServerInfo * m_ProxyLink;

    //
    // m_dwProxyVersion - a copy of the Global Proxy Version Count,
    //   the proxy link goes bad if version count changes
    //

    DWORD m_dwProxyVersion;

    //
    // m_HostScheme - Not used for direct connections,
    //   only used for matching proxy info with cached information
    //
    
    INTERNET_SCHEME m_HostScheme;

    //
    // m_HostPort - can also be, proxy port for cached proxy server
    //

    INTERNET_PORT m_HostPort;


    INTERNET_HANDLE_OBJECT *    m_pInternet;

private:             

    //
    // m_Services - bitmap of services supported by the server
    //

    union {
        struct {
            unsigned HTTP       : 1;    // HTTP server running at host
            unsigned FTP        : 1;    // FTP     "      "     "   "
            unsigned Gopher     : 1;    // gopher  "      "     "   "
            unsigned GopherPlus : 1;    // gopher+ "      "     "   "
            unsigned NNTP       : 1;    // news    "      "     "   " (future)
            unsigned SMTP       : 1;    // mail    "      "     "   " (future)
            unsigned CERN_Proxy : 1;    // server is running CERN proxy
            unsigned FTP_Proxy  : 1;    // server is running (TIS) FTP proxy/gateway
            unsigned Socks_Proxy: 1;    // server is a Socks Gateway
        } Bits;
        unsigned Word;
    } m_Services;

    //
    // m_HttpSupport - bitmap of HTTP properties supported by the server, such
    // as HTTP version, keep-alive, SSL/PCT, etc.
    //

    union {
        struct {            
            unsigned KeepAlive  : 1;    // HTTP server supports keep-alive
            unsigned Http1_0    : 1;    //  "      "       IS   HTTP/1.0
            unsigned Http1_1    : 1;    //  "      "       "    HTTP/1.1
            unsigned SSL        : 1;    // HTTP server supports SSL
            unsigned PCT        : 1;    // HTTP server supports PCT
            unsigned IIS        : 1;    // HTTP server is MS IIS (could be running others?)
            unsigned BadNS      : 1;    // HTTP server is BAD NS Enterprise server
        } Bits;
        unsigned Word;
    } m_HttpSupport;

    //
    // m_Flags - collection of boolean flags
    //

    union {
        struct {
            unsigned KA_Init    : 1;    // KeepAliveListInitialized
            unsigned Unreachable: 1;    // host (currently) unreachable
            unsigned ProxyByPassSet: 1; // has the next bit been set.
            unsigned ProxyByPassed: 1;  // did we bypass the proxy talking to this server.
            unsigned ProxyScriptCached: 1; // did/are we using this entry to cache the result of JS proxy resolution
        } Bits;
        unsigned Word;
    } m_Flags;

    //
    // m_KeepAliveList - if the server supports keep-alive, a list of the
    // keep-alive connections
    //

    SERIALIZED_LIST m_KeepAliveList;

    //
    // m_Waiters - list of sync/async waiters for connections
    //

    CPriorityList m_Waiters;

    //
    // CConnectionWaiter - private class identifying waiters in m_Waiters list
    //

    class CConnectionWaiter : public CPriorityListEntry {

    private:

        DWORD m_Sync : 1;
        DWORD m_KeepAlive : 1;
        DWORD_PTR m_dwId;
        HANDLE m_hEvent;

    public:

        CConnectionWaiter(
            IN CPriorityList *pList,
            IN BOOL      bSync,
            IN BOOL      bKeepAlive,
            IN DWORD_PTR dwId,
            IN HANDLE    hEvent,
            IN LONG      lPriority,
            OUT LPDWORD  lpdwStatus) : CPriorityListEntry(lPriority) {

            m_Sync = bSync ? 1 : 0;
            m_KeepAlive = bKeepAlive ? 1 : 0;
            m_dwId = dwId;
            m_hEvent = hEvent;
            *lpdwStatus = pList->Insert(this);
        }

        ~CConnectionWaiter() {
        }

        BOOL IsSync(VOID) {
            return (m_Sync == 1) ? TRUE : FALSE;
        }

        BOOL IsKeepAlive(VOID) {
            return (m_KeepAlive == 1) ? TRUE : FALSE;
        }

        DWORD_PTR Id(VOID) {
            return m_dwId;
        }

        VOID Signal(VOID) {
            SetEvent(m_hEvent);
        }
    };

    //
    // m_ConnectionLimit - number of simultaneous connections allowed to this
    // server
    //

    LONG m_ConnectionLimit;

    //
    // m_NewLimit - set when we need to change limit
    //

    LONG m_NewLimit;

    //
    // m_ConnectionsAvailable - number of connections available to this server.
    // If <= 0 (and not unlimited connections) then we have to wait until a
    // connection is freed
    //

    LONG m_ConnectionsAvailable;

    //
    // m_ActiveConnections - number of connections that are active. An active
    // connection is connected and sending or receiving data. This value can be
    // greater than m_ConnectionLimit if we are in the situation of being run
    // out of connections
    //

    //LONG m_ActiveConnections;

    //
    // m_LastActiveTime - timestamp (tick count) of last operation was made on
    // any connection to this server
    //

    DWORD m_LastActiveTime;

    //
    // m_ConnectTime - the average time to connect in milliseconds
    //

    DWORD m_ConnectTime;

    //
    // m_RTT - average Round Trip Time
    //

    DWORD m_RTT;

    //
    // m_AddressList - list of resolved addresses for this server
    //

    CAddressList m_AddressList;

    //
    // m_dwError - error code (mainly for constructor)
    //

    DWORD m_dwError;

#if INET_DEBUG

    DWORD m_Signature;

#define INIT_SERVER_INFO()  m_Signature = SERVER_INFO_SIGNATURE
#define CHECK_SERVER_INFO() INET_ASSERT(m_Signature == SERVER_INFO_SIGNATURE)

#else

#define INIT_SERVER_INFO()  /* NOTHING */
#define CHECK_SERVER_INFO() /* NOTHING */

#endif

    //
    // private methods
    //

    ICSocket *
    FindKeepAliveConnection(
        IN DWORD dwSocketFlags,
        IN INTERNET_PORT nPort,
        IN LPSTR pszTunnelServer
        );

    BOOL
    KeepAliveWaiters(
        VOID
        );

    BOOL
    RunOutOfConnections(
        VOID
        );

    VOID
    UpdateConnectionLimit(
        VOID
        );

public:

    CServerInfo(
        IN SERIALIZED_LIST * ServerInfoList,
        IN LPSTR lpszHostName,
        OUT DWORD* pdwError,
        IN DWORD dwService = INTERNET_SERVICE_HTTP,
        IN DWORD dwMaxConnections = WINHTTP_CONNS_PER_SERVER_UNLIMITED
        );

    ~CServerInfo();

    CServerInfo * Next(VOID) {
        return (CServerInfo *)m_List.Flink;
    }

    CServerInfo * Prev(VOID) {
        return (CServerInfo *)m_List.Blink;
    }

    // This code needs to handle system time roll over.
    // SetExpiryTime is passed the duration, and we calculate the ultimate time
    // However, this may result in a rollover -- e.g. if the current time is 
    // 0xffffff00, the ultimate time could be 0x000000fd

    // Expired is passed the current tick count, however, and in the past
    // would return TRUE immediately.
    // Thus we set a flag is we need to wait for system time rollover to happen,

    VOID SetExpiryTime(DWORD dwMilliseconds = GlobalServerInfoTimeout) {
        DWORD dw = GetTickCountWrap();
        m_Expires = dw + dwMilliseconds;
        m_Wrap = (dw > (DWORD)m_Expires);
    }

    VOID ResetExpiryTime(VOID) {
        m_Expires = 0;
        m_Wrap = 0;
    }

    BOOL Expired(LONG dwTime = (LONG)GetTickCountWrap()) {
        if (m_Wrap)
        {
            m_Wrap = ((LONG)dwTime < 0);
        }
        return (!m_Wrap && (dwTime > m_Expires)) ? TRUE : FALSE;
    }

    VOID
    Reference(
        VOID
        );

    BOOL
    Dereference(
        VOID
        );

    LONG ReferenceCount(VOID) const {
        return m_ReferenceCount;
    }

    LPSTR GetHostName(VOID) const {
        return m_HostName.StringAddress();
    }

    DWORD    
    SetCachedProxyServerInfo(
        IN CServerInfo * pProxyServer,
        IN DWORD dwProxyVersion,
        IN BOOL fUseProxy,
        IN INTERNET_SCHEME HostScheme,
        IN INTERNET_PORT HostPort,
        IN INTERNET_SCHEME ProxyScheme,
        IN INTERNET_PORT ProxyPort
        );

    CServerInfo * 
    GetCachedProxyServerInfo(
        IN INTERNET_SCHEME HostScheme,
        IN INTERNET_PORT HostPort,
        OUT BOOL *pfCachedEntry
        );

    BOOL
    CopyCachedProxyInfoToProxyMsg(
        IN OUT AUTO_PROXY_ASYNC_MSG *pQueryForProxyInfo
        );

    BOOL Match(IN DWORD dwHash, IN LPSTR lpszHostName) {
        return (dwHash == m_Hash) ? m_HostName.Strcmp(lpszHostName) : FALSE;
    }

    VOID SetHTTP(VOID) {
        m_Services.Bits.HTTP = 1;
    }

    BOOL IsHTTP(VOID) {
        return m_Services.Bits.HTTP ? TRUE : FALSE;
    }

    VOID SetFTP(VOID) {
        m_Services.Bits.FTP = 1;
    }

    BOOL IsFTP(VOID) {
        return m_Services.Bits.FTP ? TRUE : FALSE;
    }

    VOID SetGopher(VOID) {
        m_Services.Bits.Gopher = 1;
    }

    BOOL IsGopher(VOID) {
        return m_Services.Bits.Gopher ? TRUE : FALSE;
    }

    VOID SetSocksGateway(VOID) {
        m_Services.Bits.Socks_Proxy = 1;
    }

    BOOL IsSocksGateway(VOID) {
        return m_Services.Bits.Socks_Proxy ? TRUE : FALSE;
    }

    VOID SetCernProxy(VOID) {
        m_Services.Bits.CERN_Proxy = 1;
    }

    VOID SetFTPProxy(VOID) {
        m_Services.Bits.FTP_Proxy = 1;
    }

    BOOL IsCernProxy(VOID) {
        return m_Services.Bits.CERN_Proxy ? TRUE : FALSE;
    }

    VOID SetHttp1_1(VOID) {
        m_HttpSupport.Bits.Http1_1 = 1;
    }

    BOOL IsHttp1_1(VOID) {
        return m_HttpSupport.Bits.Http1_1 ? TRUE : FALSE;
    }

    VOID SetHttp1_0(VOID) {
        m_HttpSupport.Bits.Http1_0 = 1;
    }

    VOID SetBadNSServer(VOID) {
        m_HttpSupport.Bits.BadNS = 1;
    }

    BOOL IsBadNSServer(VOID) {
        return m_HttpSupport.Bits.BadNS ? TRUE : FALSE;
    }

    BOOL IsHttp1_0(VOID) {
        return m_HttpSupport.Bits.Http1_0 ? TRUE : FALSE;
    }

    VOID SetKeepAlive(VOID) {
        m_HttpSupport.Bits.KeepAlive = 1;
    }

    BOOL IsKeepAlive(VOID) {
        return m_HttpSupport.Bits.KeepAlive ? TRUE : FALSE;
    }
    
    VOID SetProxyScriptCached(BOOL fSetCached) {
        m_Flags.Bits.ProxyScriptCached = (fSetCached) ? 1 : 0;
    }

    BOOL IsProxyScriptCached(VOID) {
        return m_Flags.Bits.ProxyScriptCached ? TRUE : FALSE;
    }


    VOID SetKeepAliveListInitialized(VOID) {
        m_Flags.Bits.KA_Init = 1;
    }

    BOOL IsKeepAliveListInitialized(VOID) {
        return m_Flags.Bits.KA_Init ? TRUE : FALSE;
    }

    VOID SetUnreachable(VOID) {
        m_Flags.Bits.Unreachable = 1;
    }

    VOID SetReachable(VOID) {
        m_Flags.Bits.Unreachable = 0;
    }

    BOOL IsUnreachable(VOID) {
        return m_Flags.Bits.Unreachable ? TRUE : FALSE;
    }


    VOID SetProxyByPassed(BOOL fProxyByPassed) {
        m_Flags.Bits.ProxyByPassed = fProxyByPassed ? TRUE: FALSE;
        m_Flags.Bits.ProxyByPassSet = TRUE;
    }

    BOOL IsProxyByPassSet(VOID)
    {
        return m_Flags.Bits.ProxyByPassSet ? TRUE : FALSE;
    }

    BOOL WasProxyByPassed(VOID) {
        return m_Flags.Bits.ProxyByPassed ? TRUE : FALSE;
    }

    LONG ConnectionLimit(VOID) const {
        return m_ConnectionLimit;
    }

    VOID SetConnectionLimit(LONG Limit) {
        m_ConnectionLimit = Limit;
    }

    BOOL UnlimitedConnections(VOID) {
        return (ConnectionLimit() == WINHTTP_CONNS_PER_SERVER_UNLIMITED) ? TRUE : FALSE;
    }

    LONG KeepAliveConnections(VOID) {
        return ElementsOnSerializedList(&m_KeepAliveList);
    }

    LONG AvailableConnections(VOID) const {
        return m_ConnectionsAvailable;
    }

    LONG TotalAvailableConnections(VOID) {
        return KeepAliveConnections() + AvailableConnections();
    }

    LONG GetNewLimit(VOID) const {
        return m_NewLimit;
    }

    VOID SetNewLimit(LONG Limit) {
        m_NewLimit = Limit;
    }

    BOOL IsNewLimit(VOID) {
        return (m_ConnectionLimit != m_NewLimit) ? TRUE : FALSE;
    }

    VOID
    UpdateConnectTime(
        IN DWORD dwConnectTime
        );

    DWORD GetConnectTime(VOID) const {
        return (m_ConnectTime == (DWORD)-1) ? 0 : m_ConnectTime;
    }

    VOID
    UpdateRTT(
        IN DWORD dwTime
        );

    DWORD GetRTT(VOID) const {
        return m_RTT;
    }

    DWORD
    GetConnection_Fsm(
        IN CFsm_GetConnection * Fsm
        );

    DWORD
    ReleaseConnection(
        IN ICSocket * lpSocket OPTIONAL
        );

    VOID
    RemoveWaiter(
        IN DWORD_PTR dwId
        );

    VOID
    PurgeKeepAlives(
        IN DWORD dwForce = PKA_NO_FORCE
        );

    BOOL
    GetNextAddress(
        IN OUT LPDWORD lpdwResolutionId,
        IN OUT LPDWORD lpdwIndex,
        IN INTERNET_PORT nPort,
        OUT LPCSADDR_INFO lpAddress
        ) {
        return m_AddressList.GetNextAddress(lpdwResolutionId,
                                            lpdwIndex,
                                            nPort,
                                            lpAddress
                                            );
    }

    VOID
    InvalidateAddress(
        IN DWORD dwResolutionId,
        IN DWORD dwAddressIndex
        ) {
        m_AddressList.InvalidateAddress(dwResolutionId, dwAddressIndex);
    }

    DWORD
    ResolveHost(
        IN OUT LPDWORD lpdwResolutionId,
        IN DWORD dwFlags
        ) {
        return m_AddressList.ResolveHost(GetHostName(),
                                         lpdwResolutionId,
                                         dwFlags
                                         );
    }

    DWORD GetError(VOID) const {
        return m_dwError;
    }

    VOID SetError(DWORD dwError = GetLastError()) {
        m_dwError = dwError;
    }

    //
    // connection activity methods
    //

    VOID SetLastActiveTime(VOID) {
        m_LastActiveTime = GetTickCountWrap();
    }

    VOID ResetLastActiveTime(VOID) {
        m_LastActiveTime = 0;
    }

    DWORD GetLastActiveTime(VOID) const {
        return m_LastActiveTime;
    }

    BOOL ConnectionActivity(VOID) {

        DWORD lastActiveTime = GetLastActiveTime();

        return (lastActiveTime != 0)
                ? (((GetTickCountWrap() - lastActiveTime)
                   <= GlobalConnectionInactiveTimeout) ? TRUE : FALSE)
                : FALSE;
    }

    BOOL AllConnectionsInactive(VOID) {
        //return ((ActiveConnectionCount() >= ConnectionLimit())
        //        && !ConnectionActivity())
        //            ? TRUE
        //            : FALSE;
        return !ConnectionActivity();
    }

    //
    // friend functions
    //

    friend
    CServerInfo *
    ContainingServerInfo(
        IN LPVOID lpAddress
        );
};

//
// prototypes
//

VOID
ReleaseServerInfo(
    IN CServerInfo * lpServerInfo
    );

CServerInfo *
ContainingServerInfo(
    IN LPVOID lpAddress
    );