src/agent/adu_core_interface/src/agent_workflow.c
void ADUC_Workflow_HandleUpdateAction(ADUC_WorkflowData* workflowData)
{
    if (workflowData->UpdateActionJson == NULL)
    {
        goto done;
    }

    // We can't respond to requests from Orchestrator until we've gotten to Idle state.
    // If a call was received prior to the client going to Idle state, we'll ignore the request, and send
    // idle to the orchestrator.  When the orchestrator sees the Idle state returned, it will know that
    // the client has reset and will re-send the previous workflow request.
    if (!workflowData->StartupIdleCallSent)
    {
        Log_Warn("Ignoring Orchestrator property update as ADUC agent hasn't initialized yet.");
        goto done;
    }

    //
    // Find the WorkflowHandlerMapEntry for the desired update action.
    //

    unsigned int desiredAction;
    if (!ADUC_Json_GetUpdateAction(workflowData->UpdateActionJson, &desiredAction))
    {
        goto done;
    }

    //
    // Special case: Cancel is handled here.
    //
    // If Cancel action is received while another action (e.g. download) is in progress the agent should cancel
    // the in progress action and the agent should set Idle state.
    //
    // If an operation completes with a failed state, the error should be reported to the service, and the agent
    // should set Failed state.  The CBO once it receives the Failed state will send the agent a Cancel action to
    // indicate that it should return to Idle.  It's assumed that there is no action in progress on the agent at this
    // point and as such, the agent should set Idle state and return a success result code to the service.
    //
    //  Cancel should only be sent from the CBO when:
    // * An operation is in progress, to cancel the operation.
    // * After an operation fails to return the agent back to Idle state.
    // * A rollout end time has passed & the device has been offline and did not receive the previous command.
    //

    if (desiredAction == ADUCITF_UpdateAction_Cancel)
    {
        if (workflowData->OperationInProgress)
        {
            Log_Info("Cancel requested - notifying operation in progress.");

            // Set OperationCancelled so that when the operation in progress completes, it's clear
            // that it was due to a cancellation.
            // We will ignore the result of what the operation in progress returns when it completes
            // in cancelled state.
            workflowData->OperationCancelled = true;

            // Call upper-layer to notify of cancel request.
            ADUC_MethodCall_Cancel(workflowData);
        }
        else if (workflowData->SystemRebootState == ADUC_SystemRebootState_InProgress)
        {
            // It may not be possible, and unsafe, to try to cancel the reboot.
            // At the moment, we will ignore the cancel request.
            Log_Info("Cancel requested while system restarting - ignored");
        }
        else if (workflowData->AgentRestartState == ADUC_AgentRestartState_InProgress)
        {
            // It may not be possible, and unsafe, to try to cancel.
            // At the moment, we will ignore the cancel request.
            Log_Info("Cancel requested while agent is restarting - ignored");
        }
        else
        {
            // Cancel without an operation in progress means return to Idle state.
            Log_Info("Cancel received with no operation in progress - returning to Idle state");

            const ADUC_Result result = { .ResultCode = ADUC_IdleResult_Success };
            ADUC_SetUpdateStateWithResult(workflowData, ADUCITF_State_Idle, result);
        }

        goto done;
    }

    //
    // Not a cancel action.
    //
    const ADUC_WorkflowHandlerMapEntry* entry = GetWorkflowHandlerMapEntryForAction(desiredAction);
    if (entry == NULL)
    {
        Log_Error("Invalid UpdateAction %u -- ignoring", desiredAction);
        goto done;
    }

    //
    // Workaround:
    // Connections to the service may disconnect after a period of time (e.g. 40 minutes)
    // due to the need to refresh a token. When the reconnection occurs, all properties are re-sent
    // to the client, and as such the client might see a duplicate request, for example, another download request
    // after already processing a downloadrequest.
    // We ignore these requests because they have been handled, are currently being handled, or would be a no-op.
    //

    ADUCITF_State lastReportedState = workflowData->LastReportedState;
    _Bool isDuplicateRequest = IsDuplicateRequest(entry->Action, lastReportedState);
    if (isDuplicateRequest)
    {
        Log_Info(
            "'%s' action received. Last reported state: '%s'. Ignoring this action.",
            ADUCITF_UpdateActionToString(entry->Action),
            ADUCITF_StateToString(lastReportedState));
        goto done;
    }

    // Fail if we have already have an operation in progress.
    // This check happens after the check for duplicates, so we don't log a warning in our logs for an operation
    // that is currently being processed.
    if (workflowData->OperationInProgress)
    {
        Log_Error(
            "Cannot process action '%s' - async operation already in progress.",
            ADUCITF_UpdateActionToString(entry->Action));
        goto done;
    }

    Log_Info("Processing '%s' action", ADUCITF_UpdateActionToString(entry->Action));

    // Alloc this object on heap so that it will be valid for the entire (possibly async) operation func.
    ADUC_MethodCall_Data* methodCallData = calloc(1, sizeof(ADUC_MethodCall_Data));
    if (methodCallData == NULL)
    {
        goto done;
    }

    methodCallData->WorkflowData = workflowData;

    // workCompletionData is sent to the upper-layer which will pass the WorkCompletionToken back
    // when it makes the async work complete call.
    methodCallData->WorkCompletionData.WorkCompletionCallback = ADUC_Workflow_WorkCompletionCallback;
    methodCallData->WorkCompletionData.WorkCompletionToken = methodCallData;

    workflowData->CurrentAction = entry->Action;

    // Call into the upper-layer method to perform operation..
    workflowData->OperationInProgress = true;

    // At the start of the workflow, that is before we download, ask the upper level handler
    // to verify the metadata of the update as part of a "prepare" step

    bool shouldCallOperationFunc = true;
    ADUC_Result result;

    if (entry->Action == ADUCITF_UpdateAction_Download)
    {
        result = ADUC_MethodCall_Prepare(workflowData);
        shouldCallOperationFunc = IsAducResultCodeSuccess(result.ResultCode);
    }

    if (shouldCallOperationFunc)
    {
        result = entry->OperationFunc(methodCallData);
    }

    // Action is complete (i.e. we wont get a WorkCompletionCallback call from upper-layer) if:
    // * Upper-level did the work in a blocking manner.
    // * Method returned failure.
    // NOLINTNEXTLINE(misc-redundant-expression)
    if (!AducResultCodeIndicatesInProgress(result.ResultCode) || IsAducResultCodeFailure(result.ResultCode))
    {
        ADUC_Workflow_WorkCompletionCallback(methodCallData, result);
    }

done:
    if (workflowData->UpdateActionJson != NULL)
    {
        json_value_free(workflowData->UpdateActionJson);
        workflowData->UpdateActionJson = NULL;
    }
}
/**
 * @brief Handle an incoming update action.
 *
 * @param workflowData Workflow metadata.
 */
@brief Handle an incoming update action.
@param workflowData Workflow metadata.
@brief Handle an incoming update action . @param workflowData Workflow metadata .
void ADUC_Workflow_HandleUpdateAction(ADUC_WorkflowData* workflowData)
{
    if (workflowData->UpdateActionJson == NULL)
    {
        goto done;
    }
    if (!workflowData->StartupIdleCallSent)
    {
        Log_Warn("Ignoring Orchestrator property update as ADUC agent hasn't initialized yet.");
        goto done;
    }
     Find the WorkflowHandlerMapEntry for the desired update action.
    unsigned int desiredAction;
    if (!ADUC_Json_GetUpdateAction(workflowData->UpdateActionJson, &desiredAction))
    {
        goto done;
    }
     Special case: Cancel is handled here.
     If Cancel action is received while another action (e.g. download) is in progress the agent should cancel
     the in progress action and the agent should set Idle state.
     If an operation completes with a failed state, the error should be reported to the service, and the agent
     should set Failed state.  The CBO once it receives the Failed state will send the agent a Cancel action to
     indicate that it should return to Idle.  It's assumed that there is no action in progress on the agent at this
     point and as such, the agent should set Idle state and return a success result code to the service.
      Cancel should only be sent from the CBO when:
     * An operation is in progress, to cancel the operation.
     * After an operation fails to return the agent back to Idle state.
     * A rollout end time has passed & the device has been offline and did not receive the previous command.
    if (desiredAction == ADUCITF_UpdateAction_Cancel)
    {
        if (workflowData->OperationInProgress)
        {
            Log_Info("Cancel requested - notifying operation in progress.");
             Set OperationCancelled so that when the operation in progress completes, it's clear
             that it was due to a cancellation.
             We will ignore the result of what the operation in progress returns when it completes
             in cancelled state.
            workflowData->OperationCancelled = true;
             Call upper-layer to notify of cancel request.
            ADUC_MethodCall_Cancel(workflowData);
        }
        else if (workflowData->SystemRebootState == ADUC_SystemRebootState_InProgress)
        {
             It may not be possible, and unsafe, to try to cancel the reboot.
             At the moment, we will ignore the cancel request.
            Log_Info("Cancel requested while system restarting - ignored");
        }
        else if (workflowData->AgentRestartState == ADUC_AgentRestartState_InProgress)
        {
             It may not be possible, and unsafe, to try to cancel.
             At the moment, we will ignore the cancel request.
            Log_Info("Cancel requested while agent is restarting - ignored");
        }
        else
        {
             Cancel without an operation in progress means return to Idle state.
            Log_Info("Cancel received with no operation in progress - returning to Idle state");
            const ADUC_Result result = { .ResultCode = ADUC_IdleResult_Success };
            ADUC_SetUpdateStateWithResult(workflowData, ADUCITF_State_Idle, result);
        }
        goto done;
    }
     Not a cancel action.
    const ADUC_WorkflowHandlerMapEntry* entry = GetWorkflowHandlerMapEntryForAction(desiredAction);
    if (entry == NULL)
    {
        Log_Error("Invalid UpdateAction %u -- ignoring", desiredAction);
        goto done;
    }
     Workaround:
     Connections to the service may disconnect after a period of time (e.g. 40 minutes)
     due to the need to refresh a token. When the reconnection occurs, all properties are re-sent
     to the client, and as such the client might see a duplicate request, for example, another download request
     after already processing a downloadrequest.
     We ignore these requests because they have been handled, are currently being handled, or would be a no-op.
    ADUCITF_State lastReportedState = workflowData->LastReportedState;
    _Bool isDuplicateRequest = IsDuplicateRequest(entry->Action, lastReportedState);
    if (isDuplicateRequest)
    {
        Log_Info(
            "'%s' action received. Last reported state: '%s'. Ignoring this action.",
            ADUCITF_UpdateActionToString(entry->Action),
            ADUCITF_StateToString(lastReportedState));
        goto done;
    }
     Fail if we have already have an operation in progress.
     This check happens after the check for duplicates, so we don't log a warning in our logs for an operation
     that is currently being processed.
    if (workflowData->OperationInProgress)
    {
        Log_Error(
            "Cannot process action '%s' - async operation already in progress.",
            ADUCITF_UpdateActionToString(entry->Action));
        goto done;
    }
    Log_Info("Processing '%s' action", ADUCITF_UpdateActionToString(entry->Action));
     Alloc this object on heap so that it will be valid for the entire (possibly async) operation func.
    ADUC_MethodCall_Data* methodCallData = calloc(1, sizeof(ADUC_MethodCall_Data));
    if (methodCallData == NULL)
    {
        goto done;
    }
    methodCallData->WorkflowData = workflowData;
     workCompletionData is sent to the upper-layer which will pass the WorkCompletionToken back
     when it makes the async work complete call.
    methodCallData->WorkCompletionData.WorkCompletionCallback = ADUC_Workflow_WorkCompletionCallback;
    methodCallData->WorkCompletionData.WorkCompletionToken = methodCallData;
    workflowData->CurrentAction = entry->Action;
     Call into the upper-layer method to perform operation..
    workflowData->OperationInProgress = true;
     At the start of the workflow, that is before we download, ask the upper level handler
     to verify the metadata of the update as part of a "prepare" step
    bool shouldCallOperationFunc = true;
    ADUC_Result result;
    if (entry->Action == ADUCITF_UpdateAction_Download)
    {
        result = ADUC_MethodCall_Prepare(workflowData);
        shouldCallOperationFunc = IsAducResultCodeSuccess(result.ResultCode);
    }
    if (shouldCallOperationFunc)
    {
        result = entry->OperationFunc(methodCallData);
    }
     Action is complete (i.e. we wont get a WorkCompletionCallback call from upper-layer) if:
     * Upper-level did the work in a blocking manner.
     * Method returned failure.
     NOLINTNEXTLINE(misc-redundant-expression)
    if (!AducResultCodeIndicatesInProgress(result.ResultCode) || IsAducResultCodeFailure(result.ResultCode))
    {
        ADUC_Workflow_WorkCompletionCallback(methodCallData, result);
    }
done:
    if (workflowData->UpdateActionJson != NULL)
    {
        json_value_free(workflowData->UpdateActionJson);
        workflowData->UpdateActionJson = NULL;
    }
}
void ADUC_Workflow_HandleUpdateAction ( ADUC_WorkflowData * workflowData ) { if ( workflowData -> UpdateActionJson == NULL ) { goto done ; } if ( ! workflowData -> StartupIdleCallSent ) { Log_Warn ( " " ) ; goto done ; } unsigned int desiredAction ; if ( ! ADUC_Json_GetUpdateAction ( workflowData -> UpdateActionJson , & desiredAction ) ) { goto done ; } if ( desiredAction == ADUCITF_UpdateAction_Cancel ) { if ( workflowData -> OperationInProgress ) { Log_Info ( " " ) ; workflowData -> OperationCancelled = true ; ADUC_MethodCall_Cancel ( workflowData ) ; } else if ( workflowData -> SystemRebootState == ADUC_SystemRebootState_InProgress ) { Log_Info ( " " ) ; } else if ( workflowData -> AgentRestartState == ADUC_AgentRestartState_InProgress ) { Log_Info ( " " ) ; } else { Log_Info ( " " ) ; const ADUC_Result result = { . ResultCode = ADUC_IdleResult_Success } ; ADUC_SetUpdateStateWithResult ( workflowData , ADUCITF_State_Idle , result ) ; } goto done ; } const ADUC_WorkflowHandlerMapEntry * entry = GetWorkflowHandlerMapEntryForAction ( desiredAction ) ; if ( entry == NULL ) { Log_Error ( " " , desiredAction ) ; goto done ; } ADUCITF_State lastReportedState = workflowData -> LastReportedState ; _Bool isDuplicateRequest = IsDuplicateRequest ( entry -> Action , lastReportedState ) ; if ( isDuplicateRequest ) { Log_Info ( " " , ADUCITF_UpdateActionToString ( entry -> Action ) , ADUCITF_StateToString ( lastReportedState ) ) ; goto done ; } if ( workflowData -> OperationInProgress ) { Log_Error ( " " , ADUCITF_UpdateActionToString ( entry -> Action ) ) ; goto done ; } Log_Info ( " " , ADUCITF_UpdateActionToString ( entry -> Action ) ) ; ADUC_MethodCall_Data * methodCallData = calloc ( 1 , sizeof ( ADUC_MethodCall_Data ) ) ; if ( methodCallData == NULL ) { goto done ; } methodCallData -> WorkflowData = workflowData ; methodCallData -> WorkCompletionData . WorkCompletionCallback = ADUC_Workflow_WorkCompletionCallback ; methodCallData -> WorkCompletionData . WorkCompletionToken = methodCallData ; workflowData -> CurrentAction = entry -> Action ; workflowData -> OperationInProgress = true ; bool shouldCallOperationFunc = true ; ADUC_Result result ; if ( entry -> Action == ADUCITF_UpdateAction_Download ) { result = ADUC_MethodCall_Prepare ( workflowData ) ; shouldCallOperationFunc = IsAducResultCodeSuccess ( result . ResultCode ) ; } if ( shouldCallOperationFunc ) { result = entry -> OperationFunc ( methodCallData ) ; } if ( ! AducResultCodeIndicatesInProgress ( result . ResultCode ) || IsAducResultCodeFailure ( result . ResultCode ) ) { ADUC_Workflow_WorkCompletionCallback ( methodCallData , result ) ; } done : if ( workflowData -> UpdateActionJson != NULL ) { json_value_free ( workflowData -> UpdateActionJson ) ; workflowData -> UpdateActionJson = NULL ; } }
@brief Handle an incoming update action.
@brief Handle an incoming update action .
// We can't respond to requests from Orchestrator until we've gotten to Idle state. // If a call was received prior to the client going to Idle state, we'll ignore the request, and send // idle to the orchestrator.  When the orchestrator sees the Idle state returned, it will know that // the client has reset and will re-send the previous workflow request. // // Find the WorkflowHandlerMapEntry for the desired update action. // // // Special case: Cancel is handled here. // // If Cancel action is received while another action (e.g. download) is in progress the agent should cancel // the in progress action and the agent should set Idle state. // // If an operation completes with a failed state, the error should be reported to the service, and the agent // should set Failed state.  The CBO once it receives the Failed state will send the agent a Cancel action to // indicate that it should return to Idle.  It's assumed that there is no action in progress on the agent at this // point and as such, the agent should set Idle state and return a success result code to the service. // //  Cancel should only be sent from the CBO when: // * An operation is in progress, to cancel the operation. // * After an operation fails to return the agent back to Idle state. // * A rollout end time has passed & the device has been offline and did not receive the previous command. // // Set OperationCancelled so that when the operation in progress completes, it's clear // that it was due to a cancellation. // We will ignore the result of what the operation in progress returns when it completes // in cancelled state. // Call upper-layer to notify of cancel request. // It may not be possible, and unsafe, to try to cancel the reboot. // At the moment, we will ignore the cancel request. // It may not be possible, and unsafe, to try to cancel. // At the moment, we will ignore the cancel request. // Cancel without an operation in progress means return to Idle state. // // Not a cancel action. // // // Workaround: // Connections to the service may disconnect after a period of time (e.g. 40 minutes) // due to the need to refresh a token. When the reconnection occurs, all properties are re-sent // to the client, and as such the client might see a duplicate request, for example, another download request // after already processing a downloadrequest. // We ignore these requests because they have been handled, are currently being handled, or would be a no-op. // // Fail if we have already have an operation in progress. // This check happens after the check for duplicates, so we don't log a warning in our logs for an operation // that is currently being processed. // Alloc this object on heap so that it will be valid for the entire (possibly async) operation func. // workCompletionData is sent to the upper-layer which will pass the WorkCompletionToken back // when it makes the async work complete call. // Call into the upper-layer method to perform operation.. // At the start of the workflow, that is before we download, ask the upper level handler // to verify the metadata of the update as part of a "prepare" step // Action is complete (i.e. we wont get a WorkCompletionCallback call from upper-layer) if: // * Upper-level did the work in a blocking manner. // * Method returned failure. // NOLINTNEXTLINE(misc-redundant-expression)