LONGBOW_TEST_CASE(Local, _athenaLRUContentStore_PutManyContentObjects)
{
    AthenaLRUContentStore *impl = _createLRUContentStore();

    PARCBuffer *payload = parcBuffer_Allocate(1200);

    int numEntriesToPut = 100;
    for (int i = 0; i < numEntriesToPut; i++) {
        // Re-use the payload per contentObject, though the store will think each is seperate (for size calculations)
        CCNxContentObject *contentObject = _createContentObject("lci:/boose/roo/pie", i, payload);
        bool status = _athenaLRUContentStore_PutContentObject(impl, contentObject);
        assertTrue(status, "Expected CO %d to be put in to the store", i);
        ccnxContentObject_Release(&contentObject);
    }

    assertTrue(impl->numEntries == numEntriesToPut, "Expected the numbe of entries put in the store to match");

    // Now put the same ones (by name) again. These should kick out the old ones.

    for (int i = 0; i < numEntriesToPut; i++) {
        // Re-use the payload per contentObject, though the store will think each is seperate (for size calculations)
        CCNxContentObject *contentObject = _createContentObject("lci:/boose/roo/pie", i, payload);
        bool status = _athenaLRUContentStore_PutContentObject(impl, contentObject);
        assertTrue(status, "Expected CO %d to be put in to the store", i);
        ccnxContentObject_Release(&contentObject);
    }

    assertTrue(impl->numEntries == numEntriesToPut, "Expected the numbe of entries put in the store to match");

    parcBuffer_Release(&payload);

    athenaLRUContentStore_Display(impl, 0);

    _athenaLRUContentStore_Release((AthenaContentStoreImplementation *) &impl);
}
/**
 * Given a CCNxName, a directory path, and a requested chunk number, create a directory listing and return the specified
 * chunk of the directory listing as the payload of a newly created CCNxContentObject.
 * The new CCnxContentObject must eventually be released by calling ccnxContentObject_Release().
 *
 * @param [in] name The CCNxName to use when creating the new CCNxContentObject.
 * @param [in] directoryPath The directory whose contents are being listed.
 * @param [in] requestedChunkNumber The number of the requested chunk from the complete directory listing.
 *
 * @return A new CCNxContentObject instance containing the request chunk of the directory listing.
 */
static CCNxContentObject *
_createListResponse(CCNxName *name, const char *directoryPath, uint64_t requestedChunkNumber)
{
    CCNxContentObject *result = NULL;

    PARCBuffer *directoryList = tutorialFileIO_CreateDirectoryListing(directoryPath);

    uint64_t totalChunksInDirList = _getNumberOfChunksRequired(parcBuffer_Limit(directoryList), tutorialCommon_ChunkSize);
    if (requestedChunkNumber < totalChunksInDirList) {
        // Set the buffer's position to the start of the desired chunk.
        parcBuffer_SetPosition(directoryList, (requestedChunkNumber * tutorialCommon_ChunkSize));

        // See if we have more than 1 chunk's worth of data to in the buffer. If so, set the buffer's limit
        // to the end of the chunk.
        size_t chunkLen = parcBuffer_Remaining(directoryList);

        if (chunkLen > tutorialCommon_ChunkSize) {
            parcBuffer_SetLimit(directoryList, parcBuffer_Position(directoryList) + tutorialCommon_ChunkSize);
        }

        printf("tutorialServer: Responding to 'list' command with chunk %ld/%ld\n", (unsigned long) requestedChunkNumber, (unsigned long) totalChunksInDirList);

        // Calculate the final chunk number
        uint64_t finalChunkNumber = (totalChunksInDirList > 0) ? totalChunksInDirList - 1 : 0; // the final chunk, 0-based

        // At this point, dirListBuf has its position and limit set to the beginning and end of the
        // specified chunk.
        result = _createContentObject(name, directoryList, finalChunkNumber);
    }

    parcBuffer_Release(&directoryList);

    return result;
}
/**
 * Given a CCNxName, a directory path, a file name, and a requested chunk number, return a new CCNxContentObject
 * with that CCNxName and containing the specified chunk of the file. The new CCNxContentObject will also
 * contain the number of the last chunk required to transfer the complete file. Note that the last chunk of the
 * file being retrieved is calculated each time we retrieve a chunk so the file can be growing in size as we
 * transfer it.
 * The new CCnxContentObject must eventually be released by calling ccnxContentObject_Release().
 *
 * @param [in] name The CCNxName to use when creating the new CCNxContentObject.
 * @param [in] directoryPath The directory in which to find the specified file.
 * @param [in] fileName The name of the file.
 * @param [in] requestedChunkNumber The number of the requested chunk from the file.
 *
 * @return A new CCNxContentObject instance containing the request chunk of the specified file, or NULL if
 *         the file did not exist or was otherwise unavailable.
 */
static CCNxContentObject *
_createFetchResponse(const CCNxName *name, const char *directoryPath, const char *fileName, uint64_t requestedChunkNumber)
{
    CCNxContentObject *result = NULL;
    uint64_t finalChunkNumber = 0;

    // Combine the directoryPath and fileName into the full path name of the desired file
    size_t filePathBufferSize = strlen(fileName) + strlen(directoryPath) + 2; // +2 for '/' and trailing null.
    char *fullFilePath = parcMemory_Allocate(filePathBufferSize);
    assertNotNull(fullFilePath, "parcMemory_Allocate(%zu) returned NULL", filePathBufferSize);
    snprintf(fullFilePath, filePathBufferSize, "%s/%s", directoryPath, fileName);

    // Make sure the file exists and is accessible before creating a ContentObject response.
    if (tutorialFileIO_IsFileAvailable(fullFilePath)) {
        // Since the file's length can change (e.g. if it is being written to while we're fetching
        // it), the final chunk number can change between requests for content chunks. So, update
        // it each time this function is called.
        finalChunkNumber = _getFinalChunkNumberOfFile(fullFilePath, tutorialCommon_ChunkSize);

        // Get the actual contents of the specified chunk of the file.
        PARCBuffer *payload = tutorialFileIO_GetFileChunk(fullFilePath, tutorialCommon_ChunkSize, requestedChunkNumber);

        if (payload != NULL) {
            result = _createContentObject(name, payload, finalChunkNumber);
            parcBuffer_Release(&payload);
        }
    }

    parcMemory_Deallocate((void **) &fullFilePath);

    return result; // Could be NULL if there was no payload
}
LONGBOW_TEST_CASE(Local, putTooBig)
{
    AthenaLRUContentStoreConfig config;
    config.capacityInMB = 1;

    AthenaLRUContentStore *impl = _athenaLRUContentStore_Create(&config);

    size_t payloadSize = 2 * 1024 * 1024; // 2M

    PARCBuffer *payload = parcBuffer_Allocate(payloadSize);

    CCNxContentObject *content = _createContentObject("lci:/this/is/content", 10, payload);
    assertNotNull(content, "Expected to allocated a content object");

    bool status = _athenaLRUContentStore_PutContentObject(impl, content);
    assertFalse(status, "Expected insertion of too large a content object to fail.");

    ccnxContentObject_Release(&content);
    parcBuffer_Release(&payload);

    // Make sure that the contentobjects were added, but that the size didn't grow past the capacity
    assertTrue(impl->currentSizeInBytes == 0, "expected the current store size to be 0.");

    _athenaLRUContentStore_Release((AthenaContentStoreImplementation *) &impl);
}
LONGBOW_TEST_CASE(Local, putContentAndEnforceCapacity)
{
    AthenaLRUContentStore *impl = _createLRUContentStore();

    size_t lastSizeOfStore = 0;

    size_t payloadSize = 100 * 1024;
    _athenaLRUContentStore_SetCapacity(impl, 1); // set to 1 MB, or ~10 of our payloads

    PARCBuffer *payload = parcBuffer_Allocate(payloadSize); // 1M buffer
    int i;

    for (i = 0; i < 20; i++) {  // Add more than 10 items.
        CCNxContentObject *content = _createContentObject("lci:/this/is/content", i, payload);
        assertNotNull(content, "Expected to allocated a content object");

        bool status = _athenaLRUContentStore_PutContentObject(impl, content);
        assertTrue(status, "Expected to be able to insert content");
        assertTrue(impl->currentSizeInBytes > lastSizeOfStore, "expected store size in bytes to grow");

        ccnxContentObject_Release(&content);
    }

    // Make sure that the contentobjects were added, but that the size didn't grow past the capacity
    assertTrue(impl->currentSizeInBytes < (11 * payloadSize), "expected the current store size to be less than 11 x payload size");
    assertTrue(impl->currentSizeInBytes >= (10 * payloadSize), "expected the current store size to be roughly 10 x payload size");

    parcBuffer_Release(&payload);

    _athenaLRUContentStore_Release((AthenaContentStoreImplementation *) &impl);
}
LONGBOW_TEST_CASE(Local, _athenaLRUContentStoreEntry_CreateRelease)
{
    CCNxContentObject *contentObject = _createContentObject("lci:/boose/roo/pie", 0, NULL);
    _AthenaLRUContentStoreEntry *entry = _athenaLRUContentStoreEntry_Create(contentObject);

    _athenaLRUContentStoreEntry_Release(&entry);

    ccnxContentObject_Release(&contentObject);
}
LONGBOW_TEST_CASE(Global, putContent)
{
    AthenaLRUContentStoreConfig config;
    config.capacityInMB = 10;
    AthenaContentStore *store = athenaContentStore_Create(&AthenaContentStore_LRUImplementation, &config);

    PARCBuffer *payload = parcBuffer_WrapCString("this is a payload");
    CCNxContentObject *contentObject = _createContentObject("lci:/cakes/and/pies", 0, payload);
    parcBuffer_Release(&payload);
    ccnxContentObject_SetExpiryTime(contentObject, 100);

    athenaContentStore_PutContentObject(store, contentObject);

    athenaContentStore_Release(&store);
    ccnxContentObject_Release(&contentObject);
}
LONGBOW_TEST_CASE(EmptyImplementation, booleanApiFunctions)
{
    AthenaContentStore *store = athenaContentStore_Create(&EmptyContentStoreImplementation, NULL);

    CCNxContentObject *contentObject = _createContentObject("lci:/dogs/are/better/than/cats", 10, NULL);
    CCNxName *name = ccnxName_CreateFromURI("lci:/pie/is/always/good");
    CCNxInterest *interest = ccnxInterest_CreateSimple(name);

    //athena_EncodeMessage(interest);

    AthenaContentStore *ref = athenaContentStore_Acquire(store);
    athenaContentStore_Release(&ref);

    assertFalse(athenaContentStore_PutContentObject(store, contentObject), "Expected false from PutContentObject");
    assertFalse(athenaContentStore_GetMatch(store, interest), "Expected false from GetMatch");
    assertFalse(athenaContentStore_SetCapacity(store, 1), "Expected false from SetCapacity");
    assertFalse(athenaContentStore_RemoveMatch(store, name, NULL, NULL), "Expected false from RemoveMatch");

    ccnxName_Release(&name);
    ccnxInterest_Release(&interest);
    ccnxContentObject_Release(&contentObject);
    athenaContentStore_Release(&store);
}
LONGBOW_TEST_CASE(Local, _athenaLRUContentStore_PutContentObject)
{
    AthenaLRUContentStore *impl = _createLRUContentStore();

    PARCBuffer *payload = parcBuffer_Allocate(1200);

    CCNxContentObject *contentObject = _createContentObject("lci:/boose/roo/pie", 10, NULL);

    parcBuffer_Release(&payload);

    bool status = _athenaLRUContentStore_PutContentObject(impl, contentObject);

    assertTrue(status, "Expected to put content into the store");

    // This should replace the existing entry.
    status = _athenaLRUContentStore_PutContentObject(impl, contentObject);

    assertTrue(status, "Expected to put content into the store a second time");
    assertTrue(impl->numEntries == 1, "Expected 1 entry in the store (after implicit removal of original entry");
    assertTrue(impl->stats.numAdds == 2, "Expected stats to show 2 adds");

    ccnxContentObject_Release(&contentObject);
    _athenaLRUContentStore_Release((AthenaContentStoreImplementation *) &impl);
}
LONGBOW_TEST_CASE(Local, _athenaLRUContentStore_PutLRUContentStoreEntry)
{
    AthenaLRUContentStore *impl = _createLRUContentStore();

    PARCBuffer *payload = parcBuffer_Allocate(1200);

    CCNxContentObject *contentObject = _createContentObject("lci:/boose/roo/pie", 10, NULL);

    parcBuffer_Release(&payload);
    _AthenaLRUContentStoreEntry *entry = _athenaLRUContentStoreEntry_Create(contentObject);
    ccnxContentObject_Release(&contentObject);

    entry->expiryTime = 10000;
    entry->contentObjectHash = parcBuffer_WrapCString("object hash buffer");
    entry->keyId = parcBuffer_WrapCString("key id buffer");
    entry->hasContentObjectHash = true;
    entry->hasKeyId = true;

    bool status = _athenaLRUContentStore_PutLRUContentStoreEntry(impl, entry);

    assertTrue(status, "Expected to put content into the store");

    assertTrue(status, "Expected to put content into the store a second time");
    assertTrue(impl->numEntries == 1, "Expected 1 entry in the store");
    assertTrue(impl->stats.numAdds == 1, "Expected stats to show 1 adds");

    _athenaLRUContentStore_PurgeContentStoreEntry(impl, entry);

    assertTrue(impl->numEntries == 0, "Expected 0 entries in the store");

    parcBuffer_Release(&entry->keyId);
    parcBuffer_Release(&entry->contentObjectHash);
    _athenaLRUContentStoreEntry_Release(&entry);

    _athenaLRUContentStore_Release((AthenaContentStoreImplementation *) &impl);
}