ADUC_Result ExtensionManager::Download()

in src/extensions/extension_manager/src/extension_manager.cpp [832:987]


ADUC_Result ExtensionManager::Download(
    const ADUC_FileEntity* entity,
    WorkflowHandle workflowHandle,
    ExtensionManager_Download_Options* options,
    ADUC_DownloadProgressCallback downloadProgressCallback,
    ADUC_DownloadProcResolver downloadProcResolver)
{
    void* lib = nullptr;
    DownloadProc downloadProc = nullptr;
    SHAversion algVersion;

    ADUC_Result result = { /* .ResultCode = */ ADUC_Result_Failure, /* .ExtendedResultCode = */ 0 };
    ADUC::StringUtils::STRING_HANDLE_wrapper targetUpdateFilePath{ nullptr };

    if (!workflow_get_entity_workfolder_filepath(workflowHandle, entity, targetUpdateFilePath.address_of()))
    {
        Log_Error("Cannot construct child manifest file path.");
        result = { /* .ResultCode = */ ADUC_Result_Failure,
                   /* .ExtendedResultCode = */ ADUC_ERC_CONTENT_DOWNLOADER_BAD_CHILD_MANIFEST_FILE_PATH };
        goto done;
    }

    result = ExtensionManager::LoadContentDownloaderLibrary(&lib);
    if (IsAducResultCodeFailure(result.ResultCode))
    {
        goto done;
    }

    if (!ADUC_ContractUtils_IsV1Contract(&ExtensionManager::_contentDownloaderContractVersion))
    {
        Log_Error(
            "Unsupported contract version %d.%d",
            ExtensionManager::_contentDownloaderContractVersion.majorVer,
            ExtensionManager::_contentDownloaderContractVersion.minorVer);
        result.ResultCode = ADUC_GeneralResult_Failure;
        result.ExtendedResultCode = ADUC_ERC_CONTENT_DOWNLOADER_UNSUPPORTED_CONTRACT_VERSION;
        goto done;
    }

    downloadProc = downloadProcResolver(lib);
    if (downloadProc == nullptr)
    {
        result = { /* .ResultCode = */ ADUC_Result_Failure,
                   /* .ExtendedResultCode = */ ADUC_ERC_CONTENT_DOWNLOADER_INITIALIZEPROC_NOTIMP };
        goto done;
    }

    if (!ADUC_HashUtils_GetShaVersionForTypeString(
            ADUC_HashUtils_GetHashType(entity->Hash, entity->HashCount, 0), &algVersion))
    {
        Log_Error(
            "FileEntity for %s has unsupported hash type %s",
            targetUpdateFilePath.c_str(),
            ADUC_HashUtils_GetHashType(entity->Hash, entity->HashCount, 0));
        result.ExtendedResultCode = ADUC_ERC_CONTENT_DOWNLOADER_FILE_HASH_TYPE_NOT_SUPPORTED;
        goto done;
    }

    // If file exists and has a valid hash, then skip download.
    // Otherwise, delete an existing file, then download.
    Log_Debug("Check whether '%s' has already been download into the work folder.", targetUpdateFilePath.c_str());

    if (ADUCPAL_access(targetUpdateFilePath.c_str(), F_OK) == 0)
    {
        char* hashValue = ADUC_HashUtils_GetHashValue(entity->Hash, entity->HashCount, 0 /* index */);
        if (hashValue == nullptr)
        {
            result = { /* .ResultCode = */ ADUC_Result_Failure,
                       /* .ExtendedResultCode = */ ADUC_ERC_CONTENT_DOWNLOADER_INVALID_FILE_ENTITY_NO_HASHES };
            goto done;
        }

        // If target file exists, validate file hash.
        // If file is valid, then skip the download.
        bool validHash = ADUC_HashUtils_IsValidFileHash(
            targetUpdateFilePath.c_str(), hashValue, algVersion, false /* suppressErrorLog */);

        if (!validHash)
        {
            // Delete existing file.
            if (remove(targetUpdateFilePath.c_str()) != 0)
            {
                Log_Error("Cannot delete existing file that has invalid hash.");
                result.ExtendedResultCode = ADUC_ERC_CONTENT_DOWNLOADER_CANNOT_DELETE_EXISTING_FILE;
                goto done;
            }
        }

        result = { /* .ResultCode = */ ADUC_Result_Success, /* .ExtendedResultCode = */ 0 };
        goto done;
    }

    result.ResultCode = ADUC_Result_Failure;
    result.ExtendedResultCode = 0;

    // First, attempt to produce the update using download handler if
    // download handler exists in the entity (metadata).
    if (!IsNullOrEmpty(entity->DownloadHandlerId))
    {
        result = ProcessDownloadHandlerExtensibility(workflowHandle, entity, targetUpdateFilePath.c_str());
        // continue on to fallback to full content download if necessary
    }

    // If no download handlers specified, or download handler failed to produce the target file.
    if (IsAducResultCodeFailure(result.ResultCode)
        || result.ResultCode == ADUC_Result_Download_Handler_RequiredFullDownload)
    {
        // Either download handler id did not exist, or download handler failed and doing fallback here.
        const char* workflowId = workflow_peek_id(workflowHandle);
        cstr_wrapper workFolder{ workflow_get_workfolder(workflowHandle) };

        Log_Info("Downloading full target update payload to '%s'", targetUpdateFilePath.c_str());

        unsigned int timeoutInMinutes = GetDownloadTimeoutInMinutes(options);

        // Note: extension manager download options max timeout is in minutes,
        // but the content downloader contract version is in terms of seconds.
        unsigned int timeoutInSeconds = 60 * timeoutInMinutes;

        result = downloadProc(entity, workflowId, workFolder.get(), timeoutInSeconds, downloadProgressCallback);
        if (IsAducResultCodeFailure(result.ResultCode))
        {
            goto done;
        }
    }

    if (IsAducResultCodeSuccess(result.ResultCode))
    {
        if (!ADUC_HashUtils_IsValidFileHash(
                targetUpdateFilePath.c_str(),
                ADUC_HashUtils_GetHashValue(entity->Hash, entity->HashCount, 0),
                algVersion,
                false))
        {
            result.ResultCode = ADUC_Result_Failure;
            result.ExtendedResultCode = ADUC_ERC_CONTENT_DOWNLOADER_INVALID_FILE_HASH;

            Log_Error("Successful download of '%s' failed hash check.", targetUpdateFilePath.c_str());
            workflow_add_erc(workflowHandle, result.ExtendedResultCode);

            goto done;
        }
    }
    else
    {
        // this is defensive in case a goto done was missed above.
        goto done;
    }

    result.ResultCode = ADUC_GeneralResult_Success;
    result.ExtendedResultCode = 0;

done:

    return result;
}