/**
 * @file ecallFreqTwoIndex.cc
 * @author 
 * @brief implement the interface of frequency-two index
 * @version 0.1
 * @date 2021-01-15
 * 
 * @copyright Copyright (c) 2021
 * 
 */

#include "../../include/ecallFreqIndexTwo.h"


/**
 * @brief Construct a new Ecall Frequency Index object
 * 
 * @param maxRecvChunkNum the pointer to the outside recipe buffer
 * @param recipeBufferChunkNum the pointer to the outside current segment buffer
 */
EcallFrequencyTwoIndex::EcallFrequencyTwoIndex(uint64_t maxRecvChunkNum) : 
    EnclaveBase(maxRecvChunkNum) {
    insideDedupIndex_ = new EcallEntryHeap();
    insideDedupIndex_->SetHeapSize(topThreshold_);
    cmSketch_ = new EcallCMSketch(sketchWidth_, SKETCH_DEPTH);

    EnclaveCommon::printf("FrequencyTwoIndex: start to load the index.\n");
    if (ENABLE_SEALING) {
        if (!this->LoadDedupIndex()) {
            EnclaveCommon::printf("FrequencyTwoIndex: do not need to load the index.\n");
        }
    }
    EnclaveCommon::printf("FrequencyTwoIndex: Initial the frequency-based index.\n");

}

/**
 * @brief Destroy the Ecall Frequency Index object
 * 
 */
EcallFrequencyTwoIndex::~EcallFrequencyTwoIndex() {
    // size_t heapSize = insideDedupIndex_->Size();
    // EnclaveCommon::printf("The number of entry: %lu\n", heapSize);
    // for (size_t i = 0; i < heapSize; i++) {
    //     EnclaveCommon::printf("%u\n", insideDedupIndex_->TopEntry());
    //     if (insideDedupIndex_->Size() > 1) {
    //         insideDedupIndex_->Pop();
    //     } else {
    //         break;
    //     }
    // }
    EnclaveCommon::printf("current heap size: %ld\n", insideDedupIndex_->Size());
    if (ENABLE_SEALING) {
        this->PersistDedupIndex();
    }
    delete insideDedupIndex_;
    delete cmSketch_;
    EnclaveCommon::printf("============Destory Frequency-two index==========\n");
    EnclaveCommon::printf("logical chunk num: %lu\n", logicalChunkNum_);
    EnclaveCommon::printf("logical data size: %lu\n", logicalDataSize_);
    EnclaveCommon::printf("unique chunk num: %lu\n", uniqueChunkNum_);
    EnclaveCommon::printf("unique data size: %lu\n", uniqueDataSize_);
    EnclaveCommon::printf("compressed data size: %lu\n", compressedDataSize_); 
    EnclaveCommon::printf("inside dedup chunk num: %lu\n", insideDedupChunkNum_);
    EnclaveCommon::printf("inside dedup data size: %lu\n", insideDedupDataSize_);
#if (SGX_BREAKDOWN==1)
    EnclaveCommon::printf("data transmission: %ld\n", dataTransTime_);
    EnclaveCommon::printf("data transmission count: %ld\n", dataTransCount_);
    EnclaveCommon::printf("fingerprint: %ld\n", fingerprintTime_);
    EnclaveCommon::printf("fingerprint count: %ld\n", fingerprintCount_);
    EnclaveCommon::printf("frequency track: %ld\n", freqTime_);
    EnclaveCommon::printf("frequency track count: %ld\n", freqCount_);
    EnclaveCommon::printf("first-phase: %ld\n", firstDedupTime_);
    EnclaveCommon::printf("first-phase count: %ld\n", firstDedupCount_);
    EnclaveCommon::printf("second-phase: %ld\n", secondDedupTime_);
    EnclaveCommon::printf("second-phase count: %ld\n", secondDedupCount_);
    EnclaveCommon::printf("compression: %ld\n", compressTime_);
    EnclaveCommon::printf("compression count: %ld\n", compressCount_);
    EnclaveCommon::printf("encryption: %ld\n", encryptTime_);
    EnclaveCommon::printf("encryption count: %ld\n", encryptCount_);
    EnclaveCommon::printf("test ocall: %ld\n", testOCallTime_);
    EnclaveCommon::printf("test ocall count: %ld\n", testOCallCount_);
#endif
    EnclaveCommon::printf("=================================================\n");
}


/**
 * @brief update the inside-enclave with only freq
 * 
 * @param ChunkFp the chunk fp
 * @param currentFreq the current frequency
 */
void EcallFrequencyTwoIndex::UpdateInsideIndexFreq(const string& chunkFp, uint32_t currentFreq) {
    insideDedupIndex_->Update(chunkFp, currentFreq);
    return ;
}

/**
 * @brief process the tailed batch when received the end of the recipe flag
 * 
 * @param buffer the input buffer
 * @param payloadSize the payload size
 * @param uploadOutSGXPtr the pointer to enclave-related var
 */
void EcallFrequencyTwoIndex::ProcessTailBatch(uint8_t* buffer, uint64_t payloadSize, 
    UploadOutSGX_t* uploadOutSGXPtr) {
    int clientID = uploadOutSGXPtr->clientID;
    EnclaveClient* currentClient = EnclaveCommon::clientIndex_[clientID];
    RecipeBuffer_t* recipe = currentClient->GetRecipeBuffer();
    uint8_t* masterKey = currentClient->GetMasterKey();

    if (recipe->recipeNum != 0) {
        uint8_t cipherRecipe[maxRecvChunkNum_ * sizeof(RecipeEntry_t)];
        cryptoObj_->EncryptWithKey(currentClient->GetCipherCTX(), (uint8_t*)(recipe->recipeBuffer),
            recipe->recipeNum * sizeof(RecipeEntry_t), masterKey, cipherRecipe);
        Ocall_UpdateFileRecipe((RecipeEntry_t*)cipherRecipe, recipe->recipeNum, clientID);
    }

    // reset the recipe structure 
    recipe->recipeNum = 0;

    // update the header of the file recipe
    this->FinalizeFileRecipeHeader(buffer, payloadSize, currentClient);
    return ;
}

/**
 * @brief persist the deduplication index into the disk
 * 
 * @return true success
 * @return false fail
 */
bool EcallFrequencyTwoIndex::PersistDedupIndex() {
    size_t itemNum;
    bool persistenceStatus;
    size_t requiredBufferSize = 0;
    size_t offset = 0;
    // a pointer to store tmp buffer
    uint8_t* tmpBuffer = NULL;

    // step-1: persist the sketch state
    uint32_t** counterArrary = cmSketch_->GetCounterArray();
    Ocall_InitWriteSealedFile(&persistenceStatus, SEALED_SKETCH_TWO_PATH);
    if (persistenceStatus == false) {
        Ocall_SGX_Exit_Error("FrequencyTwoIndex: cannot init the sketch sealed file.");
    }

    for (size_t i = 0; i < SKETCH_DEPTH; i++) {
        EnclaveCommon::WriteBufferToFile((uint8_t*)counterArrary[i], sketchWidth_ * sizeof(uint32_t), SEALED_SKETCH_TWO_PATH);
    }
    Ocall_CloseWriteSealedFile(SEALED_SKETCH_TWO_PATH);

    // step-2: persist the min-heap 
    offset = 0;
    Ocall_InitWriteSealedFile(&persistenceStatus, SEALED_FREQ_TWO_INDEX_PATH);
    if (persistenceStatus == false) {
        Ocall_SGX_Exit_Error("FrequencyTwoIndex: cannot init the heap sealed file.");
    }

    vector<unordered_map<string, HeapItem_t>::iterator>* heapPtr = insideDedupIndex_->GetHeapPtr();
    itemNum = heapPtr->size();
    requiredBufferSize = sizeof(size_t) + itemNum * (CHUNK_HASH_SIZE + sizeof(HeapItem_t));
    tmpBuffer = (uint8_t*) malloc(sizeof(uint8_t) * requiredBufferSize);
    memcpy(tmpBuffer + offset, &itemNum, sizeof(size_t));
    offset += sizeof(size_t);
    for (size_t i = 0; i < itemNum; i++) {
        memcpy(tmpBuffer + offset, &(*heapPtr)[i]->first[0], CHUNK_HASH_SIZE);
        offset += CHUNK_HASH_SIZE;
        memcpy(tmpBuffer + offset, &(*heapPtr)[i]->second, sizeof(HeapItem_t));
        offset += sizeof(HeapItem_t);
    }
    EnclaveCommon::WriteBufferToFile(tmpBuffer, requiredBufferSize, SEALED_FREQ_TWO_INDEX_PATH);
    Ocall_CloseWriteSealedFile(SEALED_FREQ_TWO_INDEX_PATH);

    free(tmpBuffer);
    return true;
}

/**
 * @brief read the hook index from sealed data
 * 
 * @return true success
 * @return false fail
 */
bool EcallFrequencyTwoIndex::LoadDedupIndex() {
    size_t itemNum;
    string tmpChunkFp;
    tmpChunkFp.resize(CHUNK_HASH_SIZE, 0);
    size_t sealedDataSize;
    size_t offset = 0;

    // step-1: load the sketch state 
    uint32_t** counterArray = cmSketch_->GetCounterArray();
    Ocall_InitReadSealedFile(&sealedDataSize, SEALED_SKETCH_TWO_PATH);
    if (sealedDataSize == 0) {
        return false;
    }   

    for (size_t i = 0; i < SKETCH_DEPTH; i++) {
        EnclaveCommon::ReadFileToBuffer((uint8_t*)counterArray[i], sizeof(uint32_t) * sketchWidth_, SEALED_SKETCH_TWO_PATH);
    }
    Ocall_CloseReadSealedFile(SEALED_SKETCH_TWO_PATH);

    // step-2: load the min-heap 
    vector<unordered_map<string, HeapItem_t>::iterator>* heapPtr = insideDedupIndex_->GetHeapPtr();
    unordered_map<string, HeapItem_t>* indexPtr = insideDedupIndex_->GetIndexPtr();
    Ocall_InitReadSealedFile(&sealedDataSize, SEALED_FREQ_TWO_INDEX_PATH);
    if (sealedDataSize == 0) {
        return false;
    }

    uint8_t* tmpIndexBuffer = (uint8_t*) malloc(sealedDataSize * sizeof(uint8_t));
    EnclaveCommon::ReadFileToBuffer(tmpIndexBuffer, sealedDataSize, SEALED_FREQ_TWO_INDEX_PATH);
    memcpy(&itemNum, tmpIndexBuffer + offset, sizeof(size_t));
    offset += sizeof(size_t);
    HeapItem_t tmpItem;
    unordered_map<string, HeapItem_t>::iterator tmpIt;
    string tmpFp;
    tmpFp.resize(CHUNK_HASH_SIZE, 0);
    for (size_t i = 0; i < itemNum; i++) {
        memcpy(&tmpChunkFp[0], tmpIndexBuffer + offset, CHUNK_HASH_SIZE);
        offset += CHUNK_HASH_SIZE;
        memcpy(&tmpItem, tmpIndexBuffer + offset, sizeof(HeapItem_t));
        offset += sizeof(HeapItem_t);
        tmpIt = indexPtr->insert(make_pair(tmpChunkFp, tmpItem)).first;
        heapPtr->push_back(tmpIt);
    }
    Ocall_CloseReadSealedFile(SEALED_FREQ_TWO_INDEX_PATH);

    free(tmpIndexBuffer);
    return true; 
}


/**
 * @brief check whether add this chunk to the heap
 * 
 * @param chunkFreq the chunk freq
 */
bool EcallFrequencyTwoIndex::CheckIfAddToHeap(uint32_t chunkFreq) {
    if (insideDedupIndex_->Size() < topThreshold_) {
        return true;
    }
    // step: get the min-freq of current heap
    uint32_t currentMin = insideDedupIndex_->TopEntry();
    if (chunkFreq >= currentMin) {
        // the input chunk freq is larger than existing one, can add to the heap
        return true;
    }
    // the input chunk freq is lower than existsing one, cannot add to the heap 
    return false;
}


 /**
 * @brief Add the information of this chunk to the heap
 * 
 * @param chunkFreq the chunk freq
 * @param chunkAddress the chunk address
 * @param chunkFp the chunk fp
 */
void EcallFrequencyTwoIndex::AddChunkToHeap(uint32_t chunkFreq, const string& chunkAddress, 
    const string& chunkFp) {
    HeapItem_t tmpHeapEntry;
    // pop hte minimum item
    insideDedupIndex_->Pop();
    // insert the new one
    memcpy(&tmpHeapEntry.address, chunkAddress.c_str(), sizeof(KeyForChunkHashDB_t));
    tmpHeapEntry.chunkFreq = chunkFreq;
    insideDedupIndex_->Add(chunkFp, tmpHeapEntry);
    return ;
}


/**
 * @brief process one batch
 * 
 * @param buffer the data buffer
 * @param payloadSize the payload size
 * @param uploadOutSGXPtr the pointer to the enclave-related var 
 */
void EcallFrequencyTwoIndex::ProcessOneBatch(uint8_t* buffer, uint64_t payloadSize, 
    UploadOutSGX_t* uploadOutSGXPtr) {
    // get the client out-enclave buffer
    int clientID = uploadOutSGXPtr->clientID;

    // get the client in-enclave buffer
    EnclaveClient* currentClient = EnclaveCommon::clientIndex_[clientID];
    EVP_CIPHER_CTX* cipherCtx = currentClient->GetCipherCTX();
    EVP_MD_CTX* mdCtx = currentClient->GetMdCTX();
    uint8_t* recvBuffer = currentClient->GetRecvBuffer();
    uint8_t* sessionKey = currentClient->GetSessionKey();
    RecipeBuffer_t* recipe = currentClient->GetRecipeBuffer();
    ExchangeEntry_t* dedupStatusBase = currentClient->GetDedupExchangeBuffer();

    string tmpChunkAddressStr;
    tmpChunkAddressStr.resize(sizeof(KeyForChunkHashDB_t), 0);
    string tmpCipherAddressStr;
    tmpCipherAddressStr.resize(sizeof(KeyForChunkHashDB_t), 0);
    size_t currentOffset = sizeof(uint64_t); // jump the chunk num
    unordered_map<string, FreqOffsetEntry_t> tmpLocalIndex;

#if (SGX_BREAKDOWN==1)
    Ocall_GetCurrentTime(&startTime_);
#endif
    // step-1: decrypt the received data with the session key
    cryptoObj_->SessionKeyDec(cipherCtx, buffer, payloadSize, sessionKey, recvBuffer);
#if (SGX_BREAKDOWN==1)
    Ocall_GetCurrentTime(&endTime_);
    dataTransTime_ += (endTime_ - startTime_);
    dataTransCount_++;
#endif

    // get the chunk num
    uint64_t chunkNum = 0;
    memcpy(&chunkNum, recvBuffer, sizeof(uint64_t));

    // start to process each chunk
    uint32_t tmpChunkSize = 0;
    string tmpHashStr;
    tmpHashStr.resize(CHUNK_HASH_SIZE, 0);
    ExchangeEntry_t* currentDedupStatus = dedupStatusBase;
    ExchangeEntry_t* outsideEntryPtr = uploadOutSGXPtr->exchangeEntryList;
    size_t outsideQueryNum = 0;
    uint32_t currentFreq = 0;

    // check current min freq in the heap
    uint32_t currentMinFreq;
    HeapItem_t tmpHeapEntry;

#if (SGX_BREAKDOWN==1)
    Ocall_GetCurrentTime(&startTime_);
#endif
    if (insideDedupIndex_->Size() != 0) {
        currentMinFreq = insideDedupIndex_->TopEntry();
    } else {
        currentMinFreq = 0;
    }
#if (SGX_BREAKDOWN==1)
    Ocall_GetCurrentTime(&endTime_);
    firstDedupTime_ += (endTime_ - startTime_);
    firstDedupCount_++;
#endif

    // path one: check the inside enclave index, and update the CMSketch
    for (size_t i = 0; i < chunkNum; i++) {
        // step: compute the hash over the plaintext chunk
        // read the chunk size
        memcpy(&tmpChunkSize, recvBuffer + currentOffset, sizeof(tmpChunkSize));
        currentOffset += sizeof(tmpChunkSize);

#if (SGX_BREAKDOWN==1)
        Ocall_GetCurrentTime(&startTime_);
#endif
        cryptoObj_->GenerateHash(mdCtx, recvBuffer + currentOffset, tmpChunkSize, 
            (uint8_t*)&tmpHashStr[0]);
#if (SGX_BREAKDOWN==1)
        Ocall_GetCurrentTime(&endTime_);
        fingerprintTime_ += (endTime_ - startTime_);
        fingerprintCount_++;
#endif

#if (SGX_BREAKDOWN==1)
        Ocall_GetCurrentTime(&startTime_);
#endif
        // update the sketch 
        cmSketch_->Update((uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE, 1);

        // estimate the current frequency
        currentFreq = cmSketch_->Estimate((uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE);
#if (SGX_BREAKDOWN==1)
        Ocall_GetCurrentTime(&endTime_);
        freqTime_ += (endTime_ - startTime_);
        freqCount_++;
#endif

#if (SGX_BREAKDOWN==1 && SGX_DUPLICATE_BREAKDOWN==1) // for duplicate upload
        Ocall_GetCurrentTime(&startTime_);
#endif

        // for batching (merge them together in a batch index)
        auto localFindResult = tmpLocalIndex.find(tmpHashStr); 
        if (localFindResult != tmpLocalIndex.end()) {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
            Ocall_GetCurrentTime(&startTime_);
#endif
            // it exists in this batch index 
            ExchangeEntry_t* tmpRecord = dedupStatusBase + localFindResult->second.offset;
            switch (tmpRecord->dedupFlag) {
                case UNIQUE: {
                    // this chunk is unique for the heap, but duplicate for local index
                    currentDedupStatus->dedupFlag = TMP_UNIQUE;
                    currentDedupStatus->chunkAddress.offset = localFindResult->second.offset;
                    break ;
                }
                case DUPLICATE: {
                    // this chunk is duplicate for heap, also duplicate for the local index
                    currentDedupStatus->dedupFlag = TMP_DUPLICATE;
                    currentDedupStatus->chunkAddress.offset = localFindResult->second.offset;
                    break ;
                }
                default: {
                    Ocall_SGX_Exit_Error("Path-1: Wrong dedup flag");
                }
            }

            // update the frequency in a batch index
            localFindResult->second.freq = currentFreq;
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
            Ocall_GetCurrentTime(&endTime_);
            firstDedupTime_ += (startTime_ - endTime_);
            firstDedupCount_++;
#endif
        } else {
            // it does not exists in the batch index, compare the freq 

            // store its fingerprint for second-path heap update
            memcpy(currentDedupStatus->chunkHash, &tmpHashStr[0], CHUNK_HASH_SIZE);

            if (currentFreq < currentMinFreq) {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&startTime_);
#endif
                // its frequency is smaller than the minimum value in the heap, must not exist in the heap
                // encrypt its fingerprint, write to the outside buffer
                cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE, 
                    EnclaveCommon::indexQueryKey_, outsideEntryPtr->chunkHash); 

                // update the dedup record list
                currentDedupStatus->dedupFlag = UNIQUE;
                currentDedupStatus->chunkAddress.offset = outsideQueryNum;

                // update the outside query buffer offset 
                outsideEntryPtr++;
                outsideQueryNum++;
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&endTime_);
                secondDedupTime_ += (endTime_ - startTime_);
                secondDedupCount_++;
#endif
            } else {
                // its frequency is higher than the minimum value in the heap, check the heap
                bool heapFindResult = insideDedupIndex_->Contains(tmpHashStr);
                if (heapFindResult) {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                   Ocall_GetCurrentTime(&startTime_);
#endif
                    // it exists in the heap, directly read
                    insideDedupIndex_->GetPriority(tmpHashStr, tmpHeapEntry);

                    // update the current dedup status
                    currentDedupStatus->dedupFlag = DUPLICATE;
                    memcpy(&currentDedupStatus->chunkAddress, &tmpHeapEntry.address, sizeof(KeyForChunkHashDB_t));
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&endTime_);
                    firstDedupTime_ += (startTime_ - endTime_);
                    firstDedupCount_++;
#endif
                } else {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&startTime_);
#endif
                    // it does not exist in the heap
                    // encrypt its fingerprint, write to the outside buffer
                    cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE,
                        EnclaveCommon::indexQueryKey_, outsideEntryPtr->chunkHash);

                    // update the dedup record list
                    currentDedupStatus->dedupFlag = UNIQUE;
                    currentDedupStatus->chunkAddress.offset = outsideQueryNum;

                    // update the outside query buffer offset
                    outsideEntryPtr++;
                    outsideQueryNum++;
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&endTime_);
                    secondDedupTime_ += (endTime_ - startTime_);
                    secondDedupCount_++;
#endif
                }
            }

            // update the batch index
            FreqOffsetEntry_t tmpLocalIndexEntry;
            tmpLocalIndexEntry.freq = currentFreq;
            tmpLocalIndexEntry.offset = i; // i is the current offset in the current dedup status list
            tmpLocalIndex[tmpHashStr] = tmpLocalIndexEntry;
        }

#if (SGX_BREAKDOWN==1 && SGX_DUPLICATE_BREAKDOWN==1) // for duplicate upload
        Ocall_GetCurrentTime(&endTime_);
        firstDedupTime_ += (endTime_ - startTime_);
        firstDedupCount_++;
#endif

        currentOffset += tmpChunkSize;
        currentDedupStatus++;
    }


    // path two: check the outside enclave index
    if (outsideQueryNum != 0) {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&startTime_);
#endif
        Ocall_QueryOutsideIndex(outsideQueryNum, clientID);
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&endTime_);
        secondDedupTime_ += (endTime_ - startTime_);
        secondDedupCount_++;
#endif
    }


    currentDedupStatus = dedupStatusBase;
    outsideEntryPtr = uploadOutSGXPtr->exchangeEntryList;
    currentOffset = sizeof(uint64_t); // jump the chunk num

    ExchangeEntry_t* refEntryPtr;
    for (size_t i = 0; i < chunkNum; i++) {
        // read the chunk size
        memcpy(&tmpChunkSize, recvBuffer + currentOffset, sizeof(tmpChunkSize));
        currentOffset += sizeof(tmpChunkSize);

#if (SGX_BREAKDOWN==1 && SGX_DUPLICATE_BREAKDOWN==1) 
        Ocall_GetCurrentTime(&startTime_);
#endif
        switch (currentDedupStatus->dedupFlag) {
            case DUPLICATE: {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&startTime_);
#endif
                // it is duplicate for the min-heap
                tmpChunkAddressStr.assign((char*)&currentDedupStatus->chunkAddress,
                    sizeof(KeyForChunkHashDB_t));
                tmpHashStr.assign((char*)&currentDedupStatus->chunkHash,
                    CHUNK_HASH_SIZE);
                
                currentFreq = tmpLocalIndex[tmpHashStr].freq;
                if (insideDedupIndex_->Contains(tmpHashStr)) {
                    // it exists in the min-heap
                    this->UpdateInsideIndexFreq(tmpHashStr, currentFreq);
                } else {
                    // it does not exist in the min-heap
                    if (this->CheckIfAddToHeap(currentFreq)) {
                        // add this chunk to the heap
                        this->AddChunkToHeap(currentFreq, tmpChunkAddressStr, tmpHashStr);
                    }
                }

                // update the statistic
                insideDedupChunkNum_++;
                insideDedupDataSize_ += tmpChunkSize;
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&endTime_);
                firstDedupTime_ += (endTime_ - startTime_);
                firstDedupCount_++;
#endif
                break ;
            }
            case TMP_DUPLICATE: {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&startTime_);
#endif
                // it is also duplicate for the batch index
                refEntryPtr = dedupStatusBase + currentDedupStatus->chunkAddress.offset;
                tmpChunkAddressStr.assign((char*)&refEntryPtr->chunkAddress,
                    sizeof(KeyForChunkHashDB_t));

                // update the statistic
                insideDedupChunkNum_++;
                insideDedupDataSize_ += tmpChunkSize;
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&endTime_);
                firstDedupTime_ += (endTime_ - startTime_);
                firstDedupCount_++;
#endif
                break ;
            }
            case UNIQUE: {
                tmpHashStr.assign((char*)&currentDedupStatus->chunkHash,
                    CHUNK_HASH_SIZE);
                currentFreq = tmpLocalIndex[tmpHashStr].freq;

                if (insideDedupIndex_->Size() == topThreshold_) {
                    // the min-heap is full, may involve update
                    if (outsideEntryPtr->dedupFlag == DUPLICATE) {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                        Ocall_GetCurrentTime(&startTime_);
#endif
                        // it is duplicate for the outside index, get the return address
                        // decrypt the value
                        cryptoObj_->DecryptWithKey(cipherCtx, (uint8_t*)&outsideEntryPtr->chunkAddress,
                            sizeof(KeyForChunkHashDB_t), EnclaveCommon::indexQueryKey_, 
                            (uint8_t*)&currentDedupStatus->chunkAddress);
                        tmpChunkAddressStr.assign((char*)&currentDedupStatus->chunkAddress, sizeof(KeyForChunkHashDB_t));
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                        Ocall_GetCurrentTime(&endTime_);
                        secondDedupTime_ += (endTime_ - startTime_);
                        secondDedupCount_++;
#endif
                    } else {
                        // it is unique for the outside index
                        uniqueChunkNum_++;
                        uniqueDataSize_ += tmpChunkSize;

                        // process one unique chunk
                        this->ProcessUniqueChunk((KeyForChunkHashDB_t*)&tmpChunkAddressStr[0],
                            recvBuffer + currentOffset, tmpChunkSize, uploadOutSGXPtr, currentClient);
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                        Ocall_GetCurrentTime(&startTime_);
#endif
                        // encrypt the chunk address, and write to the outside buffer
                        cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpChunkAddressStr[0], sizeof(KeyForChunkHashDB_t),
                            EnclaveCommon::indexQueryKey_, (uint8_t*)&outsideEntryPtr->chunkAddress);
                        
                        // update the address in the record list
                        memcpy(&currentDedupStatus->chunkAddress, (uint8_t*)&tmpChunkAddressStr[0],
                            sizeof(KeyForChunkHashDB_t)); 
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                        Ocall_GetCurrentTime(&endTime_);
                        secondDedupTime_ += (endTime_ - startTime_);
                        secondDedupCount_++;
#endif
                    }

#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&startTime_);
#endif
                    if (this->CheckIfAddToHeap(currentFreq)) {
                        // add this chunk to the min-heap
                        this->AddChunkToHeap(currentFreq, tmpChunkAddressStr, tmpHashStr);
                    }
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&endTime_);
                    firstDedupTime_ += (endTime_ - startTime_);
                    firstDedupCount_++;
#endif
                } else {
                    // the min-heap is not full, directly update the heap, it must not exists in the outside 
                    uniqueChunkNum_++;
                    uniqueDataSize_ += tmpChunkSize;

                    // process one unique chunk
                    this->ProcessUniqueChunk((KeyForChunkHashDB_t*)&tmpChunkAddressStr[0], 
                        recvBuffer + currentOffset, tmpChunkSize, uploadOutSGXPtr, currentClient);
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&startTime_);
#endif
                    // update the index enclave index
                    memcpy(&tmpHeapEntry.address, tmpChunkAddressStr.c_str(), sizeof(KeyForChunkHashDB_t));
                    tmpHeapEntry.chunkFreq = currentFreq;
                    insideDedupIndex_->Add(tmpHashStr, tmpHeapEntry);
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&endTime_);
                    firstDedupTime_ += (endTime_ - startTime_);
                    firstDedupCount_++;
#endif

#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&startTime_);
#endif
                    // encrypt the chunk address, and write to the outside buffer
                    cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpChunkAddressStr[0], sizeof(KeyForChunkHashDB_t),
                        EnclaveCommon::indexQueryKey_, (uint8_t*)&outsideEntryPtr->chunkAddress);
                    
                    // update the address in the record list
                    memcpy(&currentDedupStatus->chunkAddress, (uint8_t*)&tmpChunkAddressStr[0],
                        sizeof(KeyForChunkHashDB_t));
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                    Ocall_GetCurrentTime(&endTime_);
                    secondDedupTime_ += (endTime_ - startTime_);
                    secondDedupCount_++;
#endif
                }
                outsideEntryPtr++;
                break ;
            } 
            case TMP_UNIQUE: {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&startTime_);
#endif
                // it is unique for the min-heap, but duplicate in batch index
                refEntryPtr = dedupStatusBase + currentDedupStatus->chunkAddress.offset;
                tmpChunkAddressStr.assign((char*)&refEntryPtr->chunkAddress,
                    sizeof(KeyForChunkHashDB_t));

                // update statistic
                insideDedupChunkNum_++;
                insideDedupDataSize_ += tmpChunkSize;
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
                Ocall_GetCurrentTime(&endTime_);
                firstDedupTime_ += (endTime_ - startTime_);
                firstDedupCount_++;
#endif
                break ; 
            }
            default: {
                Ocall_SGX_Exit_Error("Path-2: Wrong dedup flag.");
            }
        }
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&startTime_);
#endif
        // update the file recipe and current offset
        this->UpdateFileRecipe(tmpChunkAddressStr, *recipe, currentClient);
        currentOffset += tmpChunkSize;
        currentDedupStatus++;

#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&endTime_);
        firstDedupTime_ += (endTime_ - startTime_);
        firstDedupCount_++;
#endif
        // update the statistics
        logicalDataSize_ += tmpChunkSize;
        logicalChunkNum_++;

#if (SGX_BREAKDOWN==1 && SGX_DUPLICATE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&endTime_);
        firstDedupTime_ += (endTime_ - startTime_);
        firstDedupCount_++;
#endif
    }

    if (outsideQueryNum != 0) {
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&startTime_);
#endif
        Ocall_UpdateOutsideDedupIndex(outsideQueryNum, clientID);
#if (SGX_BREAKDOWN==1 && SGX_UNIQUE_BREAKDOWN==1)
        Ocall_GetCurrentTime(&endTime_);
        secondDedupTime_ += (endTime_ - startTime_);
        secondDedupCount_++;
#endif 
    }

    return ;
}
// /**
//  * @brief process one batch
//  * 
//  * @param buffer the data buffer
//  * @param payloadSize the payload size
//  * @param uploadOutSGXPtr the pointer to the enclave-related var 
//  */
// void EcallFrequencyTwoIndex::ProcessOneBatch(uint8_t* buffer, uint64_t payloadSize, 
//     UploadOutSGX_t* uploadOutSGXPtr) {
//     // get the client out-enclave buffer
//     int clientID = uploadOutSGXPtr->clientID;
//     Container_t* container = uploadOutSGXPtr->currentContainer;
//     void* inputMQ = uploadOutSGXPtr->inputMQ;

//     // get the client in-enclave buffer
//     EnclaveClient* currentClient = EnclaveCommon::clientIndex_[clientID];
//     EVP_CIPHER_CTX* cipherCtx = currentClient->GetCipherCTX();
//     EVP_MD_CTX* mdCtx = currentClient->GetMdCTX();
//     uint8_t* recvBuffer = currentClient->GetRecvBuffer();
//     uint8_t* sessionKey = currentClient->GetSessionKey();
//     RecipeBuffer_t* recipe = currentClient->GetRecipeBuffer();
//     ExchangeEntry_t* dedupStatusBase = currentClient->GetDedupExchangeBuffer();

//     string tmpChunkAddressStr;
//     tmpChunkAddressStr.resize(sizeof(KeyForChunkHashDB_t), 0);
//     string tmpCipherAddressStr;
//     tmpCipherAddressStr.resize(sizeof(KeyForChunkHashDB_t), 0);
//     size_t currentOffset = sizeof(uint64_t); // jump the chunk num
//     unordered_map<string, FreqOffsetEntry_t> tmpLocalIndex;

// #if (SGX_BREAKDOWN==1)
//     Ocall_GetCurrentTime(&startTime_);
// #endif
//     // step-1: decrypt the received data with the session key
//     cryptoObj_->SessionKeyDec(cipherCtx, buffer, payloadSize, sessionKey, recvBuffer);

// #if (SGX_BREAKDOWN==1)
//     Ocall_GetCurrentTime(&endTime_);
//     dataTransTime_ += (endTime_ - startTime_);
//     dataTransCount_++;
// #endif

//     // get the chunk num
//     uint64_t chunkNum = 0;
//     memcpy(&chunkNum, recvBuffer, sizeof(uint64_t));

//     // start to process each chunk
//     uint32_t tmpChunkSize = 0;
//     string tmpHashStr;
//     tmpHashStr.resize(CHUNK_HASH_SIZE, 0);
//     ExchangeEntry_t* currentDedupStatus = dedupStatusBase;
//     ExchangeEntry_t* outsideEntryPtr = uploadOutSGXPtr->exchangeEntryList;
//     size_t outsideQueryNum = 0;
//     uint32_t currentFreq = 0;

//     // check current min freq in the heap
//     uint32_t currentMinFreq;
//     HeapItem_t tmpHeapEntry;
//     string tmpCipherHashStr;
//     tmpCipherHashStr.resize(CHUNK_HASH_SIZE, 0);
//     bool status;

//     // path one: check the inside enclave index, and update the CMSketch
//     for (size_t i = 0; i < chunkNum; i++) {
//         // step: compute the hash over the plaintext chunk
//         // read the chunk size
//         memcpy(&tmpChunkSize, recvBuffer + currentOffset, sizeof(tmpChunkSize));
//         currentOffset += sizeof(tmpChunkSize);

// #if (SGX_BREAKDOWN==1)
//         Ocall_GetCurrentTime(&startTime_);
// #endif
//         cryptoObj_->GenerateHash(mdCtx, recvBuffer + currentOffset, tmpChunkSize, 
//             (uint8_t*)&tmpHashStr[0]);
// #if (SGX_BREAKDOWN==1)
//         Ocall_GetCurrentTime(&endTime_);
//         fingerprintTime_ += (endTime_ - startTime_);
//         fingerprintCount_++;
// #endif
        
// #if (SGX_BREAKDOWN==1)
//         Ocall_GetCurrentTime(&startTime_);
// #endif
//         // update the sketch 
//         cmSketch_->Update((uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE, 1);

//         // estimate the current frequency
//         currentFreq = cmSketch_->Estimate((uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE);
// #if (SGX_BREAKDOWN==1)
//         Ocall_GetCurrentTime(&endTime_);
//         freqTime_ += (endTime_ - startTime_);
//         freqCount_++;
// #endif

// #if (SGX_BREAKDOWN==1)
//         Ocall_GetCurrentTime(&startTime_);
// #endif
//         if (insideDedupIndex_->Size() == topThreshold_) {
//             currentMinFreq = insideDedupIndex_->TopEntry();
//         } else {
//             currentMinFreq = 0;
//         }
// #if (SGX_BREAKDOWN==1)
//         Ocall_GetCurrentTime(&endTime_);
//         firstDedupTime_ += (endTime_ - startTime_);
//         firstDedupCount_++;
// #endif

//         if (currentFreq < currentMinFreq) {
// #if (SGX_BREAKDOWN==1)
//             Ocall_GetCurrentTime(&startTime_);
// #endif
//             // its frequency is smaller than the minimum value in the min-heap
//             cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE, 
//                 EnclaveCommon::indexQueryKey_, (uint8_t*)&tmpCipherHashStr[0]);
            
//             status = this->ReadIndexStore(tmpCipherHashStr, tmpCipherAddressStr, clientID);

// #if (SGX_BREAKDOWN==1)
//             Ocall_GetCurrentTime(&endTime_);
//             secondDedupTime_ += (endTime_ - startTime_);
//             secondDedupCount_++;
// #endif
//             if (status == false) {
//                  // this is unique chunk
//                 // this chunk does not exist in the outside index
//                 uniqueChunkNum_++;
//                 uniqueDataSize_ += tmpChunkSize;

//                 // process one unique chunk
//                 this->ProcessUniqueChunk((KeyForChunkHashDB_t*)&tmpChunkAddressStr[0], recvBuffer + currentOffset,
//                     tmpChunkSize, cipherCtx, container, inputMQ);
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&startTime_);
// #endif
//                 // encrypt the chunk address
//                 cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpChunkAddressStr[0], sizeof(KeyForChunkHashDB_t), 
//                     EnclaveCommon::indexQueryKey_, (uint8_t*)&tmpCipherAddressStr[0]);
//                 // update the outside index
//                 this->UpdateIndexStore(tmpCipherHashStr, &tmpCipherAddressStr[0], sizeof(KeyForChunkHashDB_t));
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&endTime_);
//                 secondDedupTime_ += (endTime_ - startTime_);
//                 secondDedupCount_++;
// #endif
//             } else {
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&startTime_);
// #endif
//                  // this is duplicate chunk, decrypt the value
//                 cryptoObj_->DecryptWithKey(cipherCtx, (uint8_t*)&tmpCipherAddressStr[0], sizeof(KeyForChunkHashDB_t),
//                     EnclaveCommon::indexQueryKey_, (uint8_t*)&tmpChunkAddressStr[0]); 
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&endTime_);
//                 secondDedupTime_ += (endTime_ - startTime_);
//                 secondDedupCount_++;
// #endif
//             }
//             this->UpdateFileRecipe(tmpChunkAddressStr, *recipe, currentClient);
//         } else {
//             // check the min-heap
//             bool heapFindResult = insideDedupIndex_->Contains(tmpHashStr);
//             if (heapFindResult) {
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&startTime_);
// #endif
//                 // this chunk is duplicate in current index
//                 insideDedupIndex_->GetPriority(tmpHashStr, tmpHeapEntry);
            
//                 // update the frequency
//                 this->UpdateInsideIndexFreq(tmpHashStr, currentFreq);

//                 tmpChunkAddressStr.assign((char*)&tmpHeapEntry.address, sizeof(KeyForChunkHashDB_t));

//                 this->UpdateFileRecipe(tmpChunkAddressStr, *recipe, currentClient);

//                 insideDedupChunkNum_++;
//                 insideDedupDataSize_ += tmpChunkSize;
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&endTime_);
//                 firstDedupTime_ += (endTime_ - startTime_);
//                 firstDedupCount_++;
// #endif
//             } else {
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&startTime_);
// #endif
//                 // this chunk is non-duplicate in current index
//                 cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpHashStr[0], CHUNK_HASH_SIZE, 
//                 EnclaveCommon::indexQueryKey_, (uint8_t*)&tmpCipherHashStr[0]);
            
//                 status = this->ReadIndexStore(tmpCipherHashStr, tmpCipherAddressStr, clientID);
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&endTime_);
//                 secondDedupTime_ += (endTime_ - startTime_);
//                 secondDedupCount_++;
// #endif
//                 if (status == false) {
//                     // this is unique chunk
//                     // this chunk does not exist in the outside index
//                     uniqueChunkNum_++;
//                     uniqueDataSize_ += tmpChunkSize;

//                     // process one unique chunk
//                     this->ProcessUniqueChunk((KeyForChunkHashDB_t*)&tmpChunkAddressStr[0], recvBuffer + currentOffset,
//                         tmpChunkSize, cipherCtx, container, inputMQ);
// #if (SGX_BREAKDOWN==1)
//                     Ocall_GetCurrentTime(&startTime_);
// #endif               
//                     // encrypt the chunk address
//                     cryptoObj_->EncryptWithKey(cipherCtx, (uint8_t*)&tmpChunkAddressStr[0], sizeof(KeyForChunkHashDB_t), 
//                         EnclaveCommon::indexQueryKey_, (uint8_t*)&tmpCipherAddressStr[0]);

//                     // update the outside index
//                     this->UpdateIndexStore(tmpCipherHashStr, &tmpCipherAddressStr[0], sizeof(KeyForChunkHashDB_t));
// #if (SGX_BREAKDOWN==1)
//                     Ocall_GetCurrentTime(&endTime_);
//                     secondDedupTime_ += (endTime_ - startTime_);
//                     secondDedupCount_++;
// #endif
//                 } else {
//                     // this is duplicate chunk, decrypt the value
//                     cryptoObj_->DecryptWithKey(cipherCtx, (uint8_t*)&tmpCipherAddressStr[0], sizeof(KeyForChunkHashDB_t),
//                         EnclaveCommon::indexQueryKey_, (uint8_t*)&tmpChunkAddressStr[0]); 
//                 }

// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&startTime_);
// #endif
//                 if (insideDedupIndex_->Size() == topThreshold_) {
//                     insideDedupIndex_->Pop();
//                     tmpHeapEntry.chunkFreq = currentFreq;
//                     memcpy(&tmpHeapEntry.address, &tmpChunkAddressStr[0], sizeof(KeyForChunkHashDB_t));
//                     insideDedupIndex_->Add(tmpHashStr, tmpHeapEntry);
//                 } else {
//                     tmpHeapEntry.chunkFreq = currentFreq;
//                     memcpy(&tmpHeapEntry.address, &tmpChunkAddressStr[0], sizeof(KeyForChunkHashDB_t));
//                     insideDedupIndex_->Add(tmpHashStr, tmpHeapEntry);
//                 }

//                 this->UpdateFileRecipe(tmpChunkAddressStr, *recipe, currentClient);
// #if (SGX_BREAKDOWN==1)
//                 Ocall_GetCurrentTime(&endTime_);
//                 firstDedupTime_ += (endTime_ - startTime_);
//                 firstDedupCount_++;
// #endif
//             }
//         }
//         logicalDataSize_ += tmpChunkSize;
//         logicalChunkNum_++;
//         currentOffset += tmpChunkSize;
//     }

// #if (SGX_BREAKDOWN==1)
//     Ocall_GetCurrentTime(&startTime_);
// #endif
// #if (SGX_BREAKDOWN==1)
//     Ocall_GetCurrentTime(&endTime_);
//     testOCallTime_ += (endTime_ - startTime_);
//     testOCallCount_++;
// #endif 

//     return ;
// }