//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft
// premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license
// agreement, you are not authorized to use this source code.
// For the terms of the license, please see the license agreement
// signed by you and Microsoft.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*++


Module Name:

    scorder.cxx

Abstract:

    Small client -- implementation of message ordering class


--*/
#include <sc.hxx>
#include <scorder.hxx>
#include <svsutil.hxx>
#include <scqueue.hxx>
#include <scqman.hxx>
#include <scsman.hxx>
#include <sccomp.hxx>

unsigned int ScSequenceCollection::Hash (GUID *pguid, GUID *pOtherGuid) {
    unsigned int uiRes = 0;

    unsigned int *p1 = (unsigned int *)pguid;
    unsigned int *p2 = (unsigned int *)&pOtherGuid;

    for (int i = 0 ; i < 4 ; ++i, ++p1, ++p2)
        uiRes += (*p1) ^ (*p2);

    return uiRes % SCSEQUENCE_HASH_BUCKETS;
}

unsigned int ScSequenceCollection::Hash (WCHAR *sz) {
    int uiRes = 0;

    for ( ; ; ) {
        if (sz[0] == '\0')
            break;

        if (sz[1] == '\0') {
            uiRes += towupper (sz[0]);
            break;
        }

        uiRes += towupper (sz[0]) ^ towupper (sz[1]);
        sz += 2;
    }

    return uiRes % SCSEQUENCE_HASH_BUCKETS;
}

unsigned int ScSequenceCollection::Hash (GUID *pGuid) {
    unsigned int uiRes = 0;

    unsigned int *p1 = (unsigned int *)pGuid;

    uiRes += p1[0] ^ p1[3] + p1[1] ^ p1[2];

    return uiRes % SCSEQUENCE_HASH_BUCKETS;
}

ScOrderSeq *ScSequenceCollection::HashOrderSequence (ScQueue *pQueue, GUID *pguidStreamId, GUID *pguidQueueName, GUID *pguidOrderAckName, unsigned int uiSequenceFlags) {
//  SVSUTIL_ASSERT (pQueue->qp.bIsIncoming);
//  SVSUTIL_ASSERT (pQueue->qp.bTransactional);

    int iPos = Hash (pguidStreamId, pguidQueueName);
    ScOrderSeq *pSeq = pSeqBuckets[iPos];
    while (pSeq) {
        //
        //    Same server can be known by different names, only guid equivalence is to be tested...
        //
        if (((! pQueue) || (pSeq->p->guidQ == pQueue->qp.guid)) && (pSeq->p->guidStreamId == *pguidStreamId) && (pSeq->p->guidQueueName == *pguidQueueName)) {
            return pSeq;
        }
        pSeq = pSeq->pNextHash;
    }

    if (! pguidOrderAckName)
        return NULL;

    pSeq = (ScOrderSeq *)svsutil_GetFixed (pfmOrderSeq);

    if (! pSeq)
        return NULL;

    pSeq->p = PersistBlock();
    if (! pSeq->p) {
        svsutil_FreeFixed (pSeq, pfmOrderSeq);
        return NULL;
    }
    
    if( !pQueue) {
        ASSERT(0);
        return NULL;
    }
      
    pSeq->pQueue    = pQueue;
    pSeq->fActive   = FALSE;
    pSeq->ipAddr.S_un.S_addr = INADDR_NONE;

    pSeq->pPrevActive = NULL;
    pSeq->pNextActive = NULL;

    pSeq->pNextHash = pSeqBuckets[iPos];
    pSeqBuckets[iPos] = pSeq;

    pSeq->p->guidQ          = pQueue->qp.guid;
    pSeq->p->guidQueueName  = *pguidQueueName;
    pSeq->p->guidStreamId   = *pguidStreamId;
    pSeq->p->guidOrderAck   = *pguidOrderAckName;
    pSeq->p->llCurrentSeqID = 0;
    pSeq->p->uiCurrentSeqN  = 0;
    pSeq->p->uiLastAccessS  = 0;
    pSeq->p->uiBlockFlags   = uiSequenceFlags;

    return pSeq;
}

int ScSequenceCollection::SaveSequence (ScOrderSeq *pSeq) {
    SVSUTIL_ASSERT (pSeq->pQueue);
    SVSUTIL_ASSERT_PREFAST (pSeq->p);

    int iCluster = FindCluster (pSeq->p);
    if (iCluster < 0)
        return FALSE;

    pSeq->p->uiLastAccessS = scutil_now ();

    if ((! pSeq->fActive) && pSeq->p->llCurrentSeqID) {
        SVSUTIL_ASSERT (pSeq->p->uiCurrentSeqN);
        SVSUTIL_ASSERT(! pSeq->pPrevActive);
        SVSUTIL_ASSERT(! pSeq->pNextActive);

        pSeq->fActive     = TRUE;
        pSeq->pNextActive = pSeqActive;
        pSeqActive        = pSeq;

        if (pSeq->pNextActive)
            pSeq->pNextActive->pPrevActive = pSeq;
        else                                // Only start pulsing on the first active event...
            svsutil_SetAttrTimer (gMem->pTimer, (SVSHandle)hSequencePulse, SCSEQUENCE_R_ACKSEND * gMachine->uiOrderAckScale * 1000);
    }

    ppClusters[iCluster]->iDirty = TRUE;

    return FlushBackup ();
}

void ScSequenceCollection::DeleteQueue (ScQueue *pQueue) {
    SVSUTIL_ASSERT (gMem->IsLocked());

    if (! (pQueue->qp.bIsIncoming && pQueue->qp.bTransactional))
        return;

#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Deleting sequences associated with %s...\n", pQueue->lpszFormatName);
#endif

    for (int i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        ScOrderSeq *pSeq = pSeqBuckets[i];
        ScOrderSeq *pParentSeq = NULL;

        while (pSeq) {
            SVSUTIL_ASSERT (pSeq->p->uiLastAccessS);

            if (pSeq->pQueue == pQueue) {
                SVSUTIL_ASSERT (pSeq->p->guidQ == pQueue->qp.guid);

                if (pSeq->fActive) {
                    if (pSeq == pSeqActive) {
                        pSeqActive = pSeq->pNextActive;
                        if (pSeqActive)
                            pSeqActive->pPrevActive = NULL;
                    } else {
                        SVSUTIL_ASSERT (pSeq->pPrevActive);
                        pSeq->pPrevActive->pNextActive = pSeq->pNextActive;
                        if (pSeq->pNextActive)
                            pSeq->pNextActive->pPrevActive = pSeq->pPrevActive;
                    }
                } else
                    SVSUTIL_ASSERT ((! pSeq->pPrevActive) && (! pSeq->pNextActive));

                if (! pParentSeq)
                    pSeqBuckets[i] = pSeq->pNextHash;
                else
                    pParentSeq->pNextHash = pSeq->pNextHash;

                int iCluster = FindCluster (pSeq->p);

                SVSUTIL_ASSERT ((iCluster >= 0) && (iCluster < (int)uiClustersCount));
                SVSUTIL_ASSERT ((ppClusters[iCluster]->h.uiFirstFreeBlock < SCSEQUENCE_ENTRIES_PER_BLOCK) || (ppClusters[iCluster]->h.uiFirstFreeBlock == -1));

                ppClusters[iCluster]->iNumFree++;
                ppClusters[iCluster]->iDirty = TRUE;

                pSeq->p->uiLastAccessS = 0;
                pSeq->p->uiCurrentSeqN = ppClusters[iCluster]->h.uiFirstFreeBlock;
                ppClusters[iCluster]->h.uiFirstFreeBlock = pSeq->p - ppClusters[iCluster]->pos;

                SVSUTIL_ASSERT (ppClusters[iCluster]->h.uiFirstFreeBlock < SCSEQUENCE_ENTRIES_PER_BLOCK);

                ScOrderSeq *pSeqNext = pSeq->pNextHash;
                svsutil_FreeFixed (pSeq, pfmOrderSeq);
                pSeq = pSeqNext;
                continue;
            }

            pParentSeq = pSeq;
            pSeq = pSeq->pNextHash;
        }
    }

    FlushBackup ();
}

int ScSequenceCollection::CloseBackup (void) {
    if (hBackupFile != INVALID_HANDLE_VALUE)
        CloseHandle (hBackupFile);

    hBackupFile = INVALID_HANDLE_VALUE;

    return TRUE;
}

int ScSequenceCollection::CloseStrings (void) {
    if (hStringFile != INVALID_HANDLE_VALUE)
        CloseHandle (hStringFile);

    hStringFile = INVALID_HANDLE_VALUE;

    return TRUE;
}

int ScSequenceCollection::OpenBackup (void) {
    if (hBackupFile != INVALID_HANDLE_VALUE)
        return TRUE;

    hBackupFile = CreateFile (lpszBackupName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);

    if (hBackupFile != INVALID_HANDLE_VALUE)
        return TRUE;

#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Failed to open file %s\n", lpszBackupName);
#endif

    return FALSE;
}

int ScSequenceCollection::OpenStrings (void) {
    if (hStringFile != INVALID_HANDLE_VALUE)
        return TRUE;

    hStringFile = CreateFile (lpszStringName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);

    if (hStringFile != INVALID_HANDLE_VALUE) {
        if (SetFilePointer (hStringFile, 0, NULL, FILE_END) == 0xffffffff) {
            CloseStrings ();
            return FALSE;
        }

        return TRUE;
    }

#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Failed to open file %s\n", lpszStringName);
#endif

    return FALSE;
}

int ScSequenceCollection::FlushBackup (void) {
    for (int i = 0 ; i < (int)uiClustersCount ; ++i) {
        if (ppClusters[i]->iDirty) {
            if (! OpenBackup ())
                return FALSE;

            if (SetFilePointer (hBackupFile, i * SC_FS_UNIT, 0, FILE_BEGIN) == 0xffffffff) {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't seek to %d in %s\n", i * SC_FS_UNIT, lpszBackupName);
#endif
                return FALSE;
            }

            DWORD dwBytes = 0;

            if ((! WriteFile (hBackupFile, ppClusters[i]->ac, sizeof(ppClusters[i]->ac), &dwBytes, NULL)) ||
                (dwBytes != sizeof(ppClusters[i]->ac))) {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't write %d bytes to %s\n", sizeof(ppClusters[i]->ac), lpszBackupName);
#endif
                return FALSE;
            }

            ppClusters[i]->iDirty = FALSE;
        }
    }

    if (hBackupFile != INVALID_HANDLE_VALUE) {
        if (SetFilePointer (hBackupFile, i * SC_FS_UNIT, 0, FILE_BEGIN) == 0xffffffff) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't seek to %d in %s\n", i * SC_FS_UNIT, lpszBackupName);
#endif
            return FALSE;
        }

        SetEndOfFile (hBackupFile);
        FlushFileBuffers (hBackupFile);

        uiLastBackupAccessT = GetTickCount ();
    }

    return TRUE;
}

int ScSequenceCollection::AddNewCluster (void) {
    if (uiClustersCount == uiClustersAlloc) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Adding new sequence cluster...\n");
#endif

        unsigned int l_uiClustersAlloc = uiClustersAlloc + SCSEQUENCE_CLUSTERALLOC;
        ScOrderCluster **l_ppClusters = (ScOrderCluster **)g_funcAlloc (sizeof(ScOrderCluster *) * l_uiClustersAlloc, g_pvAllocData);
        if (! l_ppClusters) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"Failed to expand cluster array - no memory...\n");
#endif
            return FALSE;
        }

        if (ppClusters) {
            memcpy (l_ppClusters, ppClusters, sizeof(ScOrderCluster *) * uiClustersAlloc);
            g_funcFree (ppClusters, g_pvFreeData);
        }

        ppClusters = l_ppClusters;
        uiClustersAlloc = l_uiClustersAlloc;
    }

    ScOrderCluster *pCluster = (ppClusters[uiClustersCount] = (ScOrderCluster *)g_funcAlloc (sizeof(ScOrderCluster), g_pvAllocData));

    if (! pCluster) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Failed to alloc new sequence cluster...\n");
#endif
        return FALSE;
    }

    ++uiClustersCount;

    memset (&pCluster->ac, 0, sizeof(pCluster->ac));

    pCluster->iNumFree    = SCSEQUENCE_ENTRIES_PER_BLOCK;
    pCluster->iDirty      = TRUE;
    pCluster->h.uiFirstFreeBlock = 0;
    pCluster->h.uiMagic1  = SCSEQUENCE_MAGIC1;
    pCluster->h.uiMagic2  = SCSEQUENCE_MAGIC2;
    pCluster->h.uiVersion = SCSEQUENCE_VERSION;

    for (int i = 0 ; i < SCSEQUENCE_ENTRIES_PER_BLOCK - 1; ++i)
        pCluster->pos[i].uiCurrentSeqN = i + 1;

    pCluster->pos[i].uiCurrentSeqN = (DWORD)-1;

    return TRUE;
}

ScPersistOrderSeq *ScSequenceCollection::PersistBlock (void) {
    for (int i = 0 ; i < (int)uiClustersCount ; ++i) {
        if (ppClusters[i]->h.uiFirstFreeBlock != -1) {
            SVSUTIL_ASSERT (ppClusters[i]->h.uiFirstFreeBlock < SCSEQUENCE_ENTRIES_PER_BLOCK);

            ScPersistOrderSeq *p = &ppClusters[i]->pos[ppClusters[i]->h.uiFirstFreeBlock];

            SVSUTIL_ASSERT (p->uiLastAccessS == 0);
            SVSUTIL_ASSERT (p->uiCurrentSeqN < SCSEQUENCE_ENTRIES_PER_BLOCK);

            ppClusters[i]->iDirty = TRUE;
            ppClusters[i]->h.uiFirstFreeBlock = p->uiCurrentSeqN;

            memset (p, 0, sizeof(*p));
            return p;
        }
    }

    if (! AddNewCluster ())
        return NULL;

    return PersistBlock();
}

int ScSequenceCollection::Compact (void) {
    SVSUTIL_ASSERT (gMem->IsLocked());

#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Compacting %s...\n", lpszBackupName);
#endif

    uiLastCompactT = GetTickCount ();

    int iUsedSeqs = 0;
    for (int i = 0 ; i < (int)uiClustersCount ; ++i)
        iUsedSeqs += SCSEQUENCE_ENTRIES_PER_BLOCK - ppClusters[i]->iNumFree;

    int iDeltaSeqs = SCSEQUENCE_ENTRIES_PER_BLOCK * uiClustersCount - iUsedSeqs;
    if (iDeltaSeqs < (int)(SCSEQUENCE_ENTRIES_PER_BLOCK * uiClustersCount / 3))
        return TRUE;

    if (iDeltaSeqs < SCSEQUENCE_ENTRIES_PER_BLOCK)
        return TRUE;

    //
    //    We will reset the whole structure now...
    //
    unsigned int    l_uiClustersCount = uiClustersCount;
    unsigned int    l_uiClustersAlloc = uiClustersAlloc;

    ScOrderCluster    **l_ppClusters      = ppClusters;

    ppClusters      = NULL;
    uiClustersCount = 0;

    int fFaulted = FALSE;

    for (i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        ScOrderSeq *pSeq = pSeqBuckets[i];

        while (pSeq) {
            SVSUTIL_ASSERT (pSeq->p->uiLastAccessS);

            ScPersistOrderSeq *p = PersistBlock ();
            if (! p) {
                fFaulted = TRUE;
                break;
            }

            *p = *pSeq->p;
            pSeq->p = p;
            pSeq = pSeq->pNextHash;
        }

        if (fFaulted)
            break;
    }

    if (fFaulted) {
        for (i = 0 ; i < (int)uiClustersCount ; ++i)
            g_funcFree (ppClusters[i], g_pvFreeData);

        g_funcFree (ppClusters, g_pvFreeData);

        ppClusters      = l_ppClusters;
        uiClustersCount = l_uiClustersCount;
        uiClustersAlloc = l_uiClustersAlloc;

        return FALSE;
    }

    for (i = 0 ; i < (int)l_uiClustersCount ; ++i)
        g_funcFree (l_ppClusters[i], g_pvFreeData);

    g_funcFree (l_ppClusters, g_pvFreeData);

    return FlushBackup ();
}

int ScSequenceCollection::Prune (void) {
    SVSUTIL_ASSERT (gMem->IsLocked());

#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Pruning %s...\n", lpszBackupName);
#endif

    uiLastPruneT = GetTickCount ();

    int iChanges = 0;

    unsigned int uiNowS = scutil_now ();

    for (int i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        ScOrderSeq *pSeq = pSeqBuckets[i];
        ScOrderSeq *pParentSeq = NULL;

        while (pSeq) {
            SVSUTIL_ASSERT (pSeq->p->uiLastAccessS);

            if (uiNowS - pSeq->p->uiLastAccessS > gMachine->uiSeqTimeout) {
                SVSUTIL_ASSERT (! pSeq->fActive);

                ++iChanges;

                if (! pParentSeq)
                    pSeqBuckets[i] = pSeq->pNextHash;
                else
                    pParentSeq->pNextHash = pSeq->pNextHash;

                int iCluster = FindCluster (pSeq->p);

                SVSUTIL_ASSERT ((iCluster >= 0) && (iCluster < (int)uiClustersCount));
                SVSUTIL_ASSERT ((ppClusters[iCluster]->h.uiFirstFreeBlock < SCSEQUENCE_ENTRIES_PER_BLOCK) || (ppClusters[iCluster]->h.uiFirstFreeBlock == -1));

                ppClusters[iCluster]->iNumFree++;
                ppClusters[iCluster]->iDirty = TRUE;

                pSeq->p->uiLastAccessS = 0;
                pSeq->p->uiCurrentSeqN = ppClusters[iCluster]->h.uiFirstFreeBlock;
                ppClusters[iCluster]->h.uiFirstFreeBlock = pSeq->p - ppClusters[iCluster]->pos;
                
                SVSUTIL_ASSERT (ppClusters[iCluster]->h.uiFirstFreeBlock < SCSEQUENCE_ENTRIES_PER_BLOCK);

                ScOrderSeq *pSeqNext = pSeq->pNextHash;
                svsutil_FreeFixed (pSeq, pfmOrderSeq);
                pSeq = pSeqNext;
                continue;
            }

            pParentSeq = pSeq;
            pSeq = pSeq->pNextHash;
        }
    }

    int iRes = TRUE;

    if (iChanges) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Squeezed %d sequences out of %s\n", iChanges, lpszBackupName);
#endif
        iRes = Compact ();
        if (iRes)
            iRes = FlushBackup ();
    }

    PruneStringHash ();

    return iRes;
}

ScSequenceCollection::ScSequenceCollection (void) {
    hBackupFile = INVALID_HANDLE_VALUE;
    hStringFile = INVALID_HANDLE_VALUE;

    lpszBackupName = NULL;
    lpszStringName = NULL;

    uiLastBackupAccessT = uiLastStringAccessT = uiLastCompactT = uiLastPruneT = GetTickCount ();

    uiClustersCount = 0;
    uiClustersAlloc = 0;

    ppClusters = NULL;

    memset (pSeqBuckets, 0, sizeof(pSeqBuckets));
    memset (pGuidBuckets, 0, sizeof(pGuidBuckets));
    memset (pStringBuckets, 0, sizeof(pStringBuckets));

    pSeqActive = NULL;

    pfmOrderSeq = svsutil_AllocFixedMemDescr (sizeof (ScOrderCluster), SCSEQUENCE_BLOCKINCR);

    hSequencePulse = CreateEvent (NULL, FALSE, FALSE, NULL);

    fBusy = 0;
}

ScSequenceCollection::~ScSequenceCollection (void) {
    CloseBackup ();
    CloseStrings ();

    CloseHandle (hSequencePulse);

    if (lpszBackupName)
        g_funcFree (lpszBackupName, g_pvFreeData);

    if (lpszStringName)
        g_funcFree (lpszStringName, g_pvFreeData);

    if (ppClusters) {
        for (int i = 0 ; i < (int)uiClustersCount ; ++i)
            g_funcFree (ppClusters[i], g_pvFreeData);

        g_funcFree (ppClusters, g_pvFreeData);
    }

    svsutil_ReleaseFixedNonEmpty (pfmOrderSeq);

    for (int i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        while (pStringBuckets[i]) {
            StringGuidHash *pNext = pStringBuckets[i]->pNextInString;
            g_funcFree (pStringBuckets[i], g_pvFreeData);
            pStringBuckets[i] = pNext;
        }
    }

    memset (pGuidBuckets, 0, sizeof(pGuidBuckets));
}

int ScSequenceCollection::Load (void) {
    WCHAR szBuffer[_MAX_PATH];

    if (FAILED(StringCchPrintfW(szBuffer,_MAX_PATH,L"%s\\" SCSEQUENCE_FILENAME, gMachine->lpszDirName))) {
        SVSUTIL_ASSERT(0);
        return FALSE;
    }

    lpszBackupName = svsutil_wcsdup (szBuffer);
    if (!lpszBackupName)
        return FALSE;

    if (FAILED(StringCchPrintfW(szBuffer,_MAX_PATH,L"%s\\" SCSEQUENCE_FILENAME_STRING, gMachine->lpszDirName))) {
        SVSUTIL_ASSERT(0);
        return FALSE;
    }

    lpszStringName = svsutil_wcsdup (szBuffer);
    if (! lpszStringName)
        return FALSE;

    if (! LoadStringHash ()) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't load %s\n", lpszStringName);
#endif
        return FALSE;
    }

    HANDLE hFile = CreateFile (lpszBackupName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        hFile = CreateFile (lpszBackupName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_RANDOM_ACCESS, NULL);

        if (hFile == INVALID_HANDLE_VALUE) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't create %s\n", lpszBackupName);
#endif
            return FALSE;
        }

        CloseHandle (hFile);
        return TRUE;
    }

    for ( ; ; ) {
        if (! AddNewCluster()) {
            CloseHandle (hFile);
            return FALSE;
        }

        ScOrderCluster *pCluster = ppClusters[uiClustersCount - 1];

        DWORD dwBytes;

        if ((! ReadFile (hFile, pCluster->ac, sizeof(ppClusters[0]->ac), &dwBytes, NULL)) ||
            (dwBytes != sizeof(ppClusters[0]->ac))) {
            //
            //    Last cluster unused - delete...
            //
            --uiClustersCount;
            g_funcFree (pCluster, g_pvFreeData);
            break;
        }
        //
        //    Check integrity of a file
        //
        if ((pCluster->h.uiMagic1 != SCSEQUENCE_MAGIC1) ||
            (pCluster->h.uiMagic2 != SCSEQUENCE_MAGIC2) ||
            (pCluster->h.uiVersion != SCSEQUENCE_VERSION)) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"File %s corrupted - unrecognized cluster structure\n", lpszBackupName);
#endif
            CloseHandle (hFile);
            return FALSE;
        }

        pCluster->iNumFree = 0;
        pCluster->iDirty   = FALSE;

        int iFreePos = pCluster->h.uiFirstFreeBlock;
        while (iFreePos != -1) {
            if (iFreePos < 0 || iFreePos >= SCSEQUENCE_ENTRIES_PER_BLOCK) {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"File %s corrupted - free position %d out of range\n", lpszBackupName, iFreePos);
#endif
                CloseHandle (hFile);
                return FALSE;
            }

            if (pCluster->pos[iFreePos].uiLastAccessS) {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"File %s corrupted - free position %d not really free\n", lpszBackupName, iFreePos);
#endif
                CloseHandle (hFile);
                return FALSE;
            }

            ++pCluster->iNumFree;

            iFreePos = pCluster->pos[iFreePos].uiCurrentSeqN;
        }

        //
        //    Build hashes...
        //
        int iUsed = 0;

        for (int i = 0 ; i < SCSEQUENCE_ENTRIES_PER_BLOCK ; ++i) {
            if (pCluster->pos[i].uiLastAccessS) {
                ++iUsed;

                ScQueueList *pql = gQueueMan->pqlIncoming;

                ScQueue *pQueueLocal = NULL;

                while (pql) {
                    if (pql->pQueue->qp.guid == pCluster->pos[i].guidQ) {
                        pQueueLocal = pql->pQueue;
                        break;
                    }

                    pql = pql->pqlNext;
                }

                //
                //    Queue or endpoint bindings disappeared since; free the entry
                //
                if ((! pQueueLocal) || (! GetStringFromGUID (&pCluster->pos[i].guidOrderAck)) ||
                                       (! GetStringFromGUID (&pCluster->pos[i].guidQueueName))) {
#if defined (SC_VERBOSE)
                    if (! pQueueLocal)
                        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Queue for guid " SC_GUID_FORMAT L" had disappeared. \n",
                                        SC_GUID_ELEMENTS((&(pCluster->pos[i].guidQ))));

                    if (! GetStringFromGUID (&pCluster->pos[i].guidOrderAck))
                        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Order Ack Queue name for guid " SC_GUID_FORMAT L" had disappeared. \n",
                                        SC_GUID_ELEMENTS((&(pCluster->pos[i].guidOrderAck))));

                    if (! GetStringFromGUID (&pCluster->pos[i].guidOrderAck))
                        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Queue name for guid " SC_GUID_FORMAT L" had disappeared. \n",
                                        SC_GUID_ELEMENTS((&(pCluster->pos[i].guidQueueName))));

                    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Element at position %d in cluster %d is now freed\n", i, uiClustersCount);
#endif
                    --iUsed;
                    ++pCluster->iNumFree;

                    pCluster->iDirty = TRUE;

                    pCluster->pos[i].uiLastAccessS = 0;
                    pCluster->pos[i].uiCurrentSeqN = pCluster->h.uiFirstFreeBlock;

                    pCluster->h.uiFirstFreeBlock = i;

                    continue;
                }

                SVSUTIL_ASSERT (pQueueLocal->qp.bIsIncoming && pQueueLocal->qp.bTransactional);

                ScOrderSeq *pSeq = (ScOrderSeq *)svsutil_GetFixed (pfmOrderSeq);

                if (! pSeq) {
#if defined (SC_VERBOSE)
                    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't get sequence - no memory\n");
#endif
                    CloseHandle (hFile);
                    return FALSE;
                }

                int iPos = Hash (&pCluster->pos[i].guidStreamId, &pCluster->pos[i].guidQueueName);

                pSeq->pNextHash = pSeqBuckets[iPos];
                pSeqBuckets[iPos] = pSeq;

                pSeq->fActive = FALSE;
                pSeq->ipAddr.S_un.S_addr = INADDR_NONE;

                pSeq->pNextActive = NULL;
                pSeq->pPrevActive = NULL;

                pSeq->pQueue      = pQueueLocal;

                pSeq->p = &pCluster->pos[i];
            }
        }
    }

    CloseHandle (hFile);

    PruneStringHash ();

    return TRUE;
}

void ScSequenceCollection::SendAcks (void) {
#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_ORDER, L"Sending acks...\n");
#endif

    ScOrderSeq *pSeq = pSeqActive;

    unsigned int uiNowS = scutil_now ();

    while (pSeq) {
        SVSUTIL_ASSERT (pSeq->fActive);
        SVSUTIL_ASSERT_PREFAST (pSeq->p);
        SVSUTIL_ASSERT_PREFAST (pSeq->pQueue);

        ScOrderSeq *pSeqNext = pSeq->pNextActive;

        if (uiNowS - pSeq->p->uiLastAccessS < SCSEQUENCE_R_ACKPERIOD * gMachine->uiOrderAckScale) {
            SVSUTIL_ASSERT (pSeq->p->llCurrentSeqID && pSeq->p->uiCurrentSeqN);
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"Sending ack for sequence %016I64x:%d to ack queue %s from transact queue %s\n", pSeq->p->llCurrentSeqID, pSeq->p->uiCurrentSeqN, GetStringFromGUID(&pSeq->p->guidOrderAck), pSeq->pQueue->lpszFormatName);
#endif
            gQueueMan->SendOrderAck (pSeq);
        } else {    // Deactivate it...
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"Deactivating order sequence %016I64x:%d to ack queue %s from transact queue %s\n", pSeq->p->llCurrentSeqID, pSeq->p->uiCurrentSeqN, GetStringFromGUID(&pSeq->p->guidOrderAck), pSeq->pQueue->lpszFormatName);
#endif
            if (pSeq == pSeqActive) {
                SVSUTIL_ASSERT (! pSeq->pPrevActive);
                pSeqActive = pSeq->pNextActive;
            } else
                SVSUTIL_ASSERT (pSeq->pPrevActive);

            if (pSeq->pPrevActive)
                pSeq->pPrevActive->pNextActive = pSeq->pNextActive;

            if (pSeq->pNextActive)
                pSeq->pNextActive->pPrevActive = pSeq->pPrevActive;

            pSeq->fActive = FALSE;
            pSeq->pNextActive = pSeq->pPrevActive = NULL;
        }

        pSeq = pSeqNext;
    }

    if (pSeqActive)
        svsutil_SetAttrTimer (gMem->pTimer, (SVSHandle)hSequencePulse, SCSEQUENCE_R_ACKSEND * gMachine->uiOrderAckScale * 1000);
}

//
//    Persistent String-GUID map
//
StringGuidHash *ScSequenceCollection::GetHashBucket (WCHAR *sz) {
    unsigned int uiNdx = Hash (sz);
    StringGuidHash *pH = pStringBuckets[uiNdx];

    while (pH) {
        if (_wcsicmp (pH->szString, sz) == 0)
            return pH;
        pH = pH->pNextInString;
    }

    return NULL;
}

StringGuidHash *ScSequenceCollection::GetHashBucket (GUID *pguid) {
    unsigned int uiNdx = Hash (pguid);
    StringGuidHash *pH = pGuidBuckets[uiNdx];

    while (pH) {
        if (pH->guid == *pguid)
            return pH;
        pH = pH->pNextInGUID;
    }

    return NULL;
}

int ScSequenceCollection::AddNewPairToHash (WCHAR *szString, GUID *pguid, int fAddToFile) {
    if (GetHashBucket (szString)) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Duplicate string %s in hash\n", szString);
#endif
        return ERROR_DUPLICATE_TAG;
    }

    if (GetHashBucket (pguid)) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Duplicate guid " SC_GUID_FORMAT L"in hash\n", SC_GUID_ELEMENTS(pguid));
#endif
        return ERROR_DUPLICATE_TAG;
    }

    if (fAddToFile) {
        if (! OpenStrings ())
            return FALSE;

        uiLastStringAccessT = GetTickCount ();

        ScStringStoreEntry e;

        e.guid = *pguid;
        e.uiStringSize = sizeof(WCHAR) * (1 + wcslen  (szString));

        DWORD dwCurrentFilePosition = SetFilePointer (hStringFile, 0, NULL, FILE_CURRENT);

        DWORD dwWrit = 0;
        if ((! WriteFile (hStringFile, &e, offsetof(ScStringStoreEntry, szString), &dwWrit, NULL)) ||
                                        (dwWrit != offsetof(ScStringStoreEntry, szString)) ||
            (! WriteFile (hStringFile, szString, e.uiStringSize, &dwWrit, NULL)) ||
                                        (dwWrit != e.uiStringSize)) {
            if (dwCurrentFilePosition != 0xffffffff) {
                SetFilePointer (hStringFile, dwCurrentFilePosition, NULL, FILE_BEGIN);
                SetEndOfFile (hStringFile);
                FlushFileBuffers (hStringFile);
            }

            return FALSE;
        }
        FlushFileBuffers (hStringFile);
    }

    unsigned int cStringHash = Hash (szString);
    unsigned int cGuidHash = Hash (pguid);

    unsigned int cchString = 1 + wcslen(szString);
    StringGuidHash *pH = (StringGuidHash *)g_funcAlloc (offsetof (StringGuidHash, szString) + sizeof(WCHAR)*cchString, g_pvAllocData);
    if (! pH) {
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't alloc StringGuidHash! - out of memory\n");
#endif
        return ERROR_OUTOFMEMORY;
    }

    pH->guid = *pguid;
    pH->uiRef = 0;
    HRESULT hr = StringCchCopyW (pH->szString, cchString, szString);
    ASSERT(SUCCEEDED(hr));

    pH->pNextInGUID = pGuidBuckets[cGuidHash];
    pGuidBuckets[cGuidHash] = pH;

    pH->pNextInString = pStringBuckets[cStringHash];
    pStringBuckets[cStringHash] = pH;

    return ERROR_SUCCESS;
}

int ScSequenceCollection::LoadStringHash (void) {
    DWORD dwAttrib = GetFileAttributes (lpszStringName);
    if (dwAttrib == 0xffffffff) {    // Does not exist. Check for .new file
        WCHAR szNewName[_MAX_PATH];
        if (FAILED(StringCchCopyW(szNewName,_MAX_PATH,lpszStringName)) ||
            FAILED(StringCchCatW(szNewName,_MAX_PATH,L".new")))
        {
            SVSUTIL_ASSERT(0);
            return FALSE;
        }

        if (0xffffffff != GetFileAttributes (szNewName))
            MoveFile (szNewName, lpszStringName);
    }

    HANDLE hFile = CreateFile (lpszStringName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_RANDOM_ACCESS, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return FALSE;

    int iRes = FALSE;

    WCHAR szString [MSMQ_SC_MAX_URI_BYTES/sizeof(WCHAR)];

    for ( ; ; ) {
        ScStringStoreEntry e;
        DWORD dwRead = 0;
        if (! ReadFile (hFile, &e, offsetof (ScStringStoreEntry, szString), &dwRead, NULL)) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"String store corrupt: read failed (header)\n");
#endif
            break;
        }

        if (dwRead == 0) {
            iRes = TRUE;
            break;
        }

        if (dwRead != offsetof (ScStringStoreEntry, szString)) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"String store corrupt: failed reading %d bytes of header\n", offsetof (ScStringStoreEntry, szString));
#endif
            break;
        }

        if ((e.uiStringSize > sizeof(szString)) || (e.uiStringSize == 0) || (e.uiStringSize & 1)) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"String store corrupt: too big or 0-sized string (%d bytes)\n", e.uiStringSize);
#endif
            break;
        }

        if ((! ReadFile (hFile, szString, e.uiStringSize, &dwRead, NULL)) || (dwRead != e.uiStringSize)) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"String store corrupt: read failed (string)\n");
#endif
            break;
        }

        int cChar = e.uiStringSize / sizeof(WCHAR);
        if (szString[cChar - 1] != '\0') {    // MUST zero-terminate!
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"String store corrupt: string non-zero-terminated\n");
#endif
            break;
        }

        if (ERROR_SUCCESS != AddNewPairToHash (szString, &e.guid, FALSE))
            break;
    }

    CloseHandle (hFile);
    return iRes;
}

void ScSequenceCollection::PruneStringHash (void) {
    for (int i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        StringGuidHash *p = pStringBuckets[i];
        while (p) {
            p->uiRef = 0;
            p = p->pNextInString;
        }
    }

    for (i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        ScOrderSeq *pSeq = pSeqBuckets[i];
        while (pSeq) {
            StringGuidHash *pH = GetHashBucket (&pSeq->p->guidOrderAck);
            if (pH)
                pH->uiRef++;
            else {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"Error: no string for queue name guid" SC_GUID_FORMAT L"\n", SC_GUID_ELEMENTS((&pSeq->p->guidOrderAck)));
#endif
                SVSUTIL_ASSERT (0);
            }

            pH = GetHashBucket (&pSeq->p->guidQueueName);
            if (pH)
                pH->uiRef++;
            else {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"Error: no string for queue name guid" SC_GUID_FORMAT L"\n", SC_GUID_ELEMENTS((&pSeq->p->guidQueueName)));
#endif
                SVSUTIL_ASSERT (0);
            }

            pH = GetHashBucket (&pSeq->p->guidStreamId);
            if (pH)
                pH->uiRef++;
            else {
#if defined (SC_VERBOSE)
                scerror_DebugOut (VERBOSE_MASK_ORDER, L"Error: no string for streamid guid" SC_GUID_FORMAT L"\n", SC_GUID_ELEMENTS((&pSeq->p->guidStreamId)));
#endif
                SVSUTIL_ASSERT (0);
            }

            pSeq = pSeq->pNextHash;
        }
    }

    int iEmpty = 0;
    int iTotal = 0;

    for (i = 0 ; i < SCSEQUENCE_HASH_BUCKETS ; ++i) {
        StringGuidHash *p = pStringBuckets[i];
        while (p) {
            ++iTotal;
            if (p->uiRef == 0)
                ++iEmpty;

            p = p->pNextInString;
        }
    }

    if (iEmpty * SCSEQUENCE_STALEFACTOR > iTotal) {        // Rewrite
#if defined (SC_VERBOSE)
        scerror_DebugOut (VERBOSE_MASK_ORDER, L"Rewriting string hash database\n");
#endif

        CloseStrings ();

        WCHAR szNewFile[_MAX_PATH];
        HRESULT hr = StringCchCopyW(szNewFile, _countof(szNewFile), lpszStringName);
        ASSERT(SUCCEEDED(hr));
        hr = StringCchCatW (szNewFile, _countof(szNewFile), L".new");
        ASSERT(SUCCEEDED(hr));

        HANDLE hFile = CreateFile (szNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
#if defined (SC_VERBOSE)
            scerror_DebugOut (VERBOSE_MASK_ORDER, L"Can't recreate %s\n", szNewFile);
#endif
            return;
        }

        int fError = FALSE;

        for (i = 0 ; (i < SCSEQUENCE_HASH_BUCKETS) && (! fError); ++i) {
            StringGuidHash *p = pStringBuckets[i];
            while (p) {
                ++iTotal;
                if (p->uiRef != 0) {
                    ScStringStoreEntry e;

                    e.guid = p->guid;
                    e.uiStringSize = sizeof(WCHAR) * (1 + wcslen  (p->szString));
 
                    DWORD dwWrit = 0;
                    if ((! WriteFile (hStringFile, &e, offsetof(ScStringStoreEntry, szString), &dwWrit, NULL)) ||
                                        (dwWrit != offsetof(ScStringStoreEntry, szString)) ||
                        (! WriteFile (hStringFile, p->szString, e.uiStringSize, &dwWrit, NULL)) ||
                                        (dwWrit != e.uiStringSize)) {
                        fError = TRUE;
                        break;
                    }
                }

                p = p->pNextInString;
            }
        }

        CloseHandle (hFile);

        if (fError) 
            DeleteFile (szNewFile);
        else {
            DeleteFile (lpszStringName);
            MoveFile (szNewFile, lpszStringName);
        }
    }
}

WCHAR *ScSequenceCollection::GetStringFromGUID (GUID *pguid) {
    StringGuidHash *pH = GetHashBucket (pguid);
    if (! pH)
        return NULL;
    return pH->szString;
}

int ScSequenceCollection::HashStringToGUID (WCHAR *szString, GUID *pGuid) {
    StringGuidHash *pH = GetHashBucket (szString);
    if (pH) {
        *pGuid = pH->guid;
        return TRUE;
    }

    int iRes;
    do {
        scutil_uuidgen (pGuid);
        iRes = AddNewPairToHash (szString, pGuid, TRUE);
    } while (iRes == ERROR_DUPLICATE_TAG);

    return iRes == ERROR_SUCCESS ? TRUE : FALSE;
}

