/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    vfbugcheck.c

Abstract:

    This module implements support for verifier bugchecks.

Author:

    Adrian J. Oney (adriao) 20-Apr-1998

Environment:

    Kernel mode

Revision History:

    AdriaO  02/21/2000  - Moved from ntos\io\ioassert.c

--*/

//
// Disable W4 level warnings generated by public headers.
//

#include "vfpragma.h"


#include "..\io\iop.h" // Includes vfdef.h
#include "vibugcheck.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,     VfBugcheckInit)
#pragma alloc_text(PAGEVRFY, VfBugcheckThrowIoException)
#pragma alloc_text(PAGEVRFY, VfBugcheckThrowException)
#pragma alloc_text(PAGEVRFY, ViBucheckProcessParams)
#pragma alloc_text(PAGEVRFY, ViBugcheckProcessMessageText)
#pragma alloc_text(PAGEVRFY, ViBugcheckApplyControl)
#pragma alloc_text(PAGEVRFY, ViBugcheckHalt)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrintBuffer)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrintParamData)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrintUrl)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrompt)
#endif // ALLOC_PRAGMA

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEVRFD")
#endif

ULONG           ViBugCheckInitialControl;
ULONG           ViBugCheckControlOverride;
UNICODE_STRING  ViBugCheckEmptyString;

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("PAGEVRFC")
#endif

//
// When invoking the driver check macro's, pass Irps first, Routines second,
// DevObj's third, and any Status's last...
//
const DCPARAM_TYPE_ENTRY ViBugCheckParamTable[] = {
    { DCPARAM_ROUTINE, "Routine"    },
    { DCPARAM_IRP,     "Irp"        },
    { DCPARAM_IRPSNAP, "Snapshot"   },
    { DCPARAM_DEVOBJ,  "DevObj"     },
    { DCPARAM_STATUS,  "Status"     },
    { DCPARAM_ULONG,   "Ulong"      },
    { DCPARAM_PVOID,   "Pvoid"      }
};


VOID
FASTCALL
VfBugcheckInit(
    VOID
    )
/*++

Routine Description:

    This routine initializes the verifier bugcheck support routines.

Arguments:

    None.

Return Value:

    None.

--*/
{
    ViBugCheckInitialControl = 0;
    ViBugCheckControlOverride = 0;
    RtlInitUnicodeString(&ViBugCheckEmptyString, NULL);
}


NTSTATUS
VfBugcheckThrowIoException(
    IN DCERROR_ID           MessageID,
    IN ULONG                MessageParameterMask,
    ...
    )
/*++

   Description:

      This routine processes an assert and provides options for removing the
      breakpoint, changing to just a text-out, etc.

    DCPARAM_IRP*(count)+DCPARAM_ROUTINE*(count)+DCPARAM_DEVOBJ*(count),
        irp1,
        irp2,
        irp3,
        routine1,
        ..,
        ..,
        devobj1,

    count can be a max of 3.

   Notes:

      The text will automagically be formatted and printed as such:

      ASSERTION CLASS: ASSERTION TEXT ASSERTION TEXT ASSERTION
                       TEXT ASSERTION TEXT ...

--*/
{
    PVFMESSAGE_TEMPLATE_TABLE ioVerifierTable;
    UCHAR paramFormat[9*3*ARRAY_COUNT(ViBugCheckParamTable)+1];
    ULONG paramType, paramMask, curMask;
    NTSTATUS status;
    va_list arglist;

    curMask = MessageParameterMask;
    paramFormat[0] = '\0';

    for(paramType=0; paramType<ARRAY_COUNT(ViBugCheckParamTable); paramType++) {

        paramMask = ViBugCheckParamTable[paramType].DcParamMask;
        while(curMask & (paramMask*3)) {

            strcat(
                (char *) paramFormat,
                ViBugCheckParamTable[paramType].DcParamName
                );

            curMask -= paramMask;
        }
    }

    VfMessageRetrieveInternalTable(
        VFMESSAGE_TABLE_IOVERIFIER,
        &ioVerifierTable
        );

    va_start(arglist, MessageParameterMask);

    status = VfBugcheckThrowException(
        ioVerifierTable,
        (VFMESSAGE_ERRORID) MessageID,
        (PCSTR) paramFormat,
        &arglist
        );

    va_end(arglist);

    return status;
}


NTSTATUS
VfBugcheckThrowException(
    IN PVFMESSAGE_TEMPLATE_TABLE    MessageTable        OPTIONAL,
    IN VFMESSAGE_ERRORID            MessageID,
    IN PCSTR                        MessageParamFormat,
    IN va_list *                    MessageParameters
    )
/*++

   Description:

      This routine displays an assert and provides options for
      removing the breakpoint, changing to just a text-out, etc.

   Arguments:

   Notes:

      The text will automagically be formatted and printed as such:

      ASSERTION CLASS: ASSERTION TEXT ASSERTION TEXT ASSERTION
                       TEXT ASSERTION TEXT ...

--*/
{
    UCHAR finalBuffer[512];
    NTSTATUS status;
    DC_CHECK_DATA dcCheckData;
    PVOID dcParamArray[3*ARRAY_COUNT(ViBugCheckParamTable)];
    BOOLEAN exitAssertion;

    //
    // Preinit
    //
    RtlZeroMemory(dcParamArray, sizeof(dcParamArray));

    //
    // Determine what our basic policy towards this check will be and fill out
    // the dcCheckData structure as well as we can.
    //
    ViBucheckProcessParams(
        MessageTable,
        MessageID,
        MessageParamFormat,
        MessageParameters,
        dcParamArray,
        &dcCheckData
        );

    if (!ViBugcheckApplyControl(&dcCheckData)) {

        //
        // Nothing to see here, just ignore the assert...
        //
        return STATUS_SUCCESS;
    }

    //
    // We are going to express our disatifaction somehow. Expand out the
    // message we've prepared for this scenario.
    //
    status = ViBugcheckProcessMessageText(
        sizeof(finalBuffer),
        (PSTR)finalBuffer,
        &dcCheckData
        );

    if (!NT_SUCCESS(status)) {

        ASSERT(0);

        //
        // Something went wrong with the index lookup!
        //
        return status;
    }

    do {

        ViBugcheckPrintBuffer(&dcCheckData);
        ViBugcheckPrintParamData(&dcCheckData);
        ViBugcheckPrintUrl(&dcCheckData);
        ViBugcheckHalt(&dcCheckData);
        ViBugcheckPrompt(&dcCheckData, &exitAssertion);

    } while (!exitAssertion);

    return status;
}


VOID
ViBucheckProcessParams(
    IN  PVFMESSAGE_TEMPLATE_TABLE   MessageTable        OPTIONAL,
    IN  VFMESSAGE_ERRORID           MessageID,
    IN  PCSTR                       MessageParamFormat,
    IN  va_list *                   MessageParameters,
    IN  PVOID *                     DcParamArray,
    OUT PDC_CHECK_DATA              DcCheckData
    )
{
    PVOID culpritAddress;
    ULONG i, paramType, paramLen;
    char ansiDriverName[81];
    NTSTATUS status;
    ULONG paramIndices[ARRAY_COUNT(ViBugCheckParamTable)];
    PCSTR format;

    //
    // First we grab parameter off the stack and slot them appropriately into
    // our array of "things".
    //
    // The array is in groups of three for each possible member of a given type
    // (three irps, three routines, three device objects, etc). Items not
    // referenced in are set to NULL.
    //
    RtlZeroMemory(paramIndices, sizeof(paramIndices));
    format = MessageParamFormat;
    while(*format) {

        if ((format[0] == ' ')||(format[0] == '%')) {

            format++;
            continue;
        }

        for(paramType = 0;
            paramType < ARRAY_COUNT(ViBugCheckParamTable);
            paramType++) {

            paramLen = (ULONG)strlen(ViBugCheckParamTable[paramType].DcParamName);

            if (!_strnicmp(ViBugCheckParamTable[paramType].DcParamName, format, paramLen)) {

                //
                // Match! Advance the pointer...
                //
                format += paramLen;

                //
                // If the caller specified an index, grab it. Otherwise infer.
                //
                if ((format[0] >= '1') && (format[0] <= '3')) {

                    i = format[0] - '1';
                    format++;

                } else {

                    i = paramIndices[paramType];
                    ASSERT(i < 3);
                }

                if (i < 3) {

                    //
                    // Param number is within bounds.
                    //
                    DcParamArray[paramType*3+i] = va_arg(*MessageParameters, PVOID);
                }

                //
                // Update the current parameter index for the given type.
                //
                paramIndices[paramType] = i+1;

                //
                // Get out early
                //
                break;
            }
        }

        if (paramType == ARRAY_COUNT(ViBugCheckParamTable)) {

            //
            // We could'nt find an entry matching the format text. Bail.
            //
            ASSERT(paramType != ARRAY_COUNT(ViBugCheckParamTable));
            break;
        }
    }

    //
    // Pre-init unhelpful answers...
    //
    DcCheckData->DriverName = &ViBugCheckEmptyString;
    DcCheckData->OffsetIntoImage = 0;
    DcCheckData->InVerifierList = TRUE;
    culpritAddress = DcParamArray[0];

    //
    // Extract the culprit's name if possible...
    //
    if (culpritAddress) {

        status = KevUtilAddressToFileHeader(
            (PVOID) culpritAddress,
            (PUINT_PTR)(&DcCheckData->OffsetIntoImage),
            &DcCheckData->DriverName,
            &DcCheckData->InVerifierList
            );

        if (!NT_SUCCESS(status)) {

            //
            // IF we don't know who it is, assert anyway.
            //
            DcCheckData->InVerifierList = TRUE;
        }
    }

    //
    // Record
    //
    DcCheckData->CulpritAddress = culpritAddress;
    DcCheckData->DcParamArray = DcParamArray;
    DcCheckData->MessageID = MessageID;

    //
    // Get an ANSI version of the driver name.
    //
    KeBugCheckUnicodeToAnsi(
        DcCheckData->DriverName,
        ansiDriverName,
        sizeof(ansiDriverName)
        );

    //
    // Retrieve a pointer to the appropriate message data.
    //
    VfMessageRetrieveErrorData(
        MessageTable,
        MessageID,
        ansiDriverName,
        &DcCheckData->BugCheckMajor,
        &DcCheckData->AssertionClass,
        &DcCheckData->MessageTextTemplate,
        &DcCheckData->Control
        );
}


NTSTATUS
FASTCALL
ViBugcheckProcessMessageText(
    IN ULONG               MaxOutputBufferSize,
    OUT PSTR               OutputBuffer,
    IN OUT PDC_CHECK_DATA  DcCheckData
    )
{
    ULONG paramType, maxParameterTypes;
    ULONG arrayIndex, paramLength;
    char const* messageHead;
    PSTR newMessage;
    LONG charsRemaining, length;

    //
    // Get the message text.
    //
    messageHead = DcCheckData->MessageTextTemplate;

    //
    // Now manually build out the message.
    //
    newMessage = OutputBuffer;
    charsRemaining = (MaxOutputBufferSize/sizeof(UCHAR))-1;
    maxParameterTypes = ARRAY_COUNT(ViBugCheckParamTable);

    while(*messageHead != '\0') {

        if (charsRemaining <= 0) {

            return STATUS_BUFFER_OVERFLOW;
        }

        if (*messageHead != '%') {

            *newMessage = *messageHead;
            newMessage++;
            messageHead++;
            charsRemaining--;

        } else {

            for(paramType = 0; paramType < maxParameterTypes; paramType++) {

                paramLength = (ULONG)strlen(ViBugCheckParamTable[paramType].DcParamName);

                //
                // Do we have a match?
                //
                // N.B. - We don't do any case 'de-sensitizing' anywhere, so
                //        everything's cases must match!
                //
                if (RtlCompareMemory(
                    messageHead+1,
                    ViBugCheckParamTable[paramType].DcParamName,
                    paramLength*sizeof(UCHAR)) == paramLength*sizeof(UCHAR)) {

                    arrayIndex = paramType*3;
                    messageHead += (paramLength+1);

                    //
                    // Was an index passed in (ie, "3rd" irp requested)?
                    //
                    if ((*messageHead >= '1') && (*messageHead <= '3')) {

                        //
                        // Adjust table index appropriately.
                        //
                        arrayIndex += (*messageHead - '1') ;
                        messageHead++;
                    }

                    if ((arrayIndex < 6) || (arrayIndex >=9)) {

                        //
                        // Normal param, print the pointer
                        //
                        length = _snprintf(
                            newMessage,
                            charsRemaining+1,
                            "%p",
                            DcCheckData->DcParamArray[arrayIndex]
                            );

                    } else {

                        //
                        // IRP Snapshot, extract the IRP and print that
                        //
                        length = _snprintf(
                            newMessage,
                            charsRemaining+1,
                            "%p",
                            ((PIRP_SNAPSHOT) DcCheckData->DcParamArray[arrayIndex])->Irp
                            );
                    }

                    if (length == -1) {

                        return STATUS_BUFFER_OVERFLOW;
                    }

                    charsRemaining -= length;
                    newMessage += length;
                    break;
                }
            }

            if (paramType == maxParameterTypes) {

                //
                // Either the message we looked up is malformed, we don't recognize
                // the %thing it is talking about, or this is %%!
                //
                *newMessage = *messageHead;
                messageHead++;
                newMessage++;
                charsRemaining--;

                if (*messageHead == '%') {

                    messageHead++;
                }
            }
        }
    }

    //
    // Null-terminate it (we have room because we took one off the buffer size
    // above).
    //
    *newMessage = '\0';

    DcCheckData->ClassText = DcCheckData->AssertionClass->MessageClassText;
    DcCheckData->AssertionText = OutputBuffer;
    return STATUS_SUCCESS;
}


BOOLEAN
FASTCALL
ViBugcheckApplyControl(
    IN OUT PDC_CHECK_DATA  DcCheckData
    )
{
    ULONG assertionControl;

    if (ViBugCheckControlOverride) {

        assertionControl = ViBugCheckControlOverride;

    } else if (DcCheckData->Control) {

        //
        // Initialize the control if appropo
        //
        if (!((*DcCheckData->Control) & VFM_FLAG_INITIALIZED)) {

            *DcCheckData->Control |= (
                VFM_FLAG_INITIALIZED | ViBugCheckInitialControl |
                DcCheckData->AssertionClass->ClassFlags );
        }

        assertionControl = *DcCheckData->Control;

    } else {

        assertionControl =
            ( ViBugCheckInitialControl |
              DcCheckData->AssertionClass->ClassFlags );
    }

    if (assertionControl & VFM_FLAG_CLEARED) {

        //
        // If the breakpoint was cleared, then return, print/rip not.
        //
        return FALSE;
    }

    if ((!(assertionControl & VFM_IGNORE_DRIVER_LIST)) &&
        (!DcCheckData->InVerifierList)) {

        //
        // Not of interest, skip this one.
        //
        return FALSE;
    }

    //
    // If there is no debugger, don't halt the machine. We are probably
    // ripping like mad and the user just wants to be able to boot.
    // The one exception is if VFM_DEPLOYMENT_FAILURE is set. Then we shall
    // invoke the driver bugcheck...
    //
    if ((!KdDebuggerEnabled) && (!(assertionControl & VFM_DEPLOYMENT_FAILURE))) {

        return FALSE;
    }

    //
    // Record our intentions and continue.
    //
    DcCheckData->AssertionControl = assertionControl;
    return TRUE;
}


VOID
FASTCALL
ViBugcheckHalt(
    IN PDC_CHECK_DATA DcCheckData
    )
{
    PVOID parameterArray[4];
    char captionBuffer[256];
    char ansiDriverName[81];

    //
    // Do not bugcheck if a kernel debugger is attached, nor if this isn't a
    // fatal error.
    //
    if (KdDebuggerEnabled ||
        (!(DcCheckData->AssertionControl & VFM_DEPLOYMENT_FAILURE))) {

        return;
    }

    //
    // We are here because VFM_DEPLOYMENT_FAILURE is set. We use
    // FATAL_UNHANDLED_HARD_ERROR so that we can give a
    // descriptive text string for the problem.
    //
    parameterArray[0] = (PVOID)(ULONG_PTR)(DcCheckData->MessageID);
    parameterArray[1] = DcCheckData->CulpritAddress;
    parameterArray[2] = DcCheckData->DcParamArray[3];
    parameterArray[3] = DcCheckData->DcParamArray[9];

    if (DcCheckData->BugCheckMajor == DRIVER_VERIFIER_IOMANAGER_VIOLATION) {

        KeBugCheckUnicodeToAnsi(
            DcCheckData->DriverName,
            ansiDriverName,
            sizeof(ansiDriverName)
            );

        _snprintf(
            captionBuffer,
            sizeof(captionBuffer),
            "IO SYSTEM VERIFICATION ERROR in %s (%s %x)\n[%s+%x at %p]\n",
            ansiDriverName,
            DcCheckData->ClassText,
            DcCheckData->MessageID,
            ansiDriverName,
            DcCheckData->OffsetIntoImage,
            DcCheckData->CulpritAddress
            );

        KeBugCheckEx(
            FATAL_UNHANDLED_HARD_ERROR,
            DcCheckData->BugCheckMajor,
            (ULONG_PTR) parameterArray,
            (ULONG_PTR) captionBuffer,
            (ULONG_PTR) "" // DcCheckData->AssertionText is too technical
            );

    } else {

        KeBugCheckEx(
            DcCheckData->BugCheckMajor,
            DcCheckData->MessageID,
            (ULONG_PTR) DcCheckData->DcParamArray[9],
            (ULONG_PTR) DcCheckData->DcParamArray[15],
            (ULONG_PTR) DcCheckData->DcParamArray[16]
            );
    }
}


VOID
FASTCALL
ViBugcheckPrintBuffer(
    IN PDC_CHECK_DATA DcCheckData
    )
{
    UCHAR buffer[82];
    UCHAR classBuf[81];
    UCHAR callerBuf[81+40];
    UCHAR ansiDriverName[81];
    LONG  lMargin, i, lMarginCur, rMargin=78;
    PSTR lineStart, lastWord, current, lMarginText;

    //
    // Put down a carriage return
    //
    DbgPrint("\n") ;

    //
    // Drop a banner if this is a fatal assert or a logo failure.
    //
    if (DcCheckData->AssertionControl &
        (VFM_DEPLOYMENT_FAILURE | VFM_LOGO_FAILURE)) {

        DbgPrint(
            "***********************************************************************\n"
            "* THIS VALIDATION BUG IS FATAL AND WILL CAUSE THE VERIFIER TO HALT    *\n"
            "* WINDOWS (BUGCHECK) WHEN THE MACHINE IS NOT UNDER A KERNEL DEBUGGER! *\n"
            "***********************************************************************\n"
            "\n"
            );
    }

    //
    // Prepare left margin (ClassText)
    //
    if (DcCheckData->ClassText != NULL) {

        lMargin = (LONG)strlen(DcCheckData->ClassText)+2;

        DbgPrint("%s: ", DcCheckData->ClassText);

    } else {

        lMargin = 0;
    }

    if (lMargin+1>=rMargin) {

        lMargin=0;
    }

    for(i=0; i<lMargin; i++) classBuf[i] = ' ';
    classBuf[lMargin] = '\0';
    lMarginText = (PSTR)(classBuf+lMargin);
    lMarginCur = lMargin;

    lineStart = lastWord = current = DcCheckData->AssertionText;

    //
    // Print out culprit if we have him...
    //
    if (DcCheckData->CulpritAddress) {

        if (DcCheckData->DriverName->Length) {

            KeBugCheckUnicodeToAnsi(
                DcCheckData->DriverName,
                (PSTR)ansiDriverName,
                sizeof(ansiDriverName)
                );

            sprintf((PCHAR)callerBuf, "[%s @ 0x%p] ",
                ansiDriverName,
                DcCheckData->CulpritAddress
                );

        } else {

            sprintf((PCHAR)callerBuf, "[0x%p] ", DcCheckData->CulpritAddress);
        }

        DbgPrint("%s", callerBuf);
        lMarginCur += (LONG)strlen((PCHAR)callerBuf);
    }

    //
    // Format and print our assertion text
    //
    while(*current) {

        if (*current == ' ') {

            if ((current - lineStart) >= (rMargin-lMarginCur-1)) {

                DbgPrint("%s", lMarginText);
                lMarginText = (PSTR)classBuf;
                lMarginCur = lMargin;

                if ((lastWord-lineStart)<rMargin) {

                    memcpy(buffer, lineStart, (ULONG)(lastWord-lineStart)*sizeof(UCHAR));
                    buffer[lastWord-lineStart] = '\0';
                    DbgPrint("%s\n", buffer);

                }

                lineStart = lastWord+1;
            }

            lastWord = current;
        }

        current++;
    }

    if ((current - lineStart) >= (rMargin-lMarginCur-1)) {

        DbgPrint("%s", lMarginText);
        lMarginText = (PSTR)classBuf;

        if ((lastWord-lineStart)<rMargin) {

            memcpy(buffer, lineStart, (ULONG)(lastWord-lineStart)*sizeof(UCHAR));
            buffer[lastWord-lineStart] = '\0';
            DbgPrint("%s\n", buffer);
        }

        lineStart = lastWord+1;
    }

    if (lineStart<current) {

        DbgPrint("%s%s\n", lMarginText, lineStart);
    }
}


VOID
FASTCALL
ViBugcheckPrintParamData(
    IN PDC_CHECK_DATA DcCheckData
    )
{
    if (DcCheckData->DcParamArray[3]) {

        VfPrintDumpIrp((PIRP) DcCheckData->DcParamArray[3]);
    }

    if (DcCheckData->DcParamArray[6]) {

        VfPrintDumpIrpStack(
            &((PIRP_SNAPSHOT) DcCheckData->DcParamArray[6])->IoStackLocation
            );
    }
}


VOID
FASTCALL
ViBugcheckPrintUrl(
    IN PDC_CHECK_DATA DcCheckData
    )
{
    DbgPrint(
        "http://www.microsoft.com/hwdq/bc/default.asp?os=%d.%d.%d&major=0x%x&minor=0x%x&lang=0x%x\n",
        VER_PRODUCTMAJORVERSION,
        VER_PRODUCTMINORVERSION,
        VER_PRODUCTBUILD,
        DcCheckData->BugCheckMajor,
        DcCheckData->MessageID,
        9 // English
        );
}


VOID
FASTCALL
ViBugcheckPrompt(
    IN      PDC_CHECK_DATA  DcCheckData,
    OUT     PBOOLEAN        ExitAssertion
    )
{
    char response[2];
    ULONG assertionControl;
    BOOLEAN waitForInput;

    assertionControl = DcCheckData->AssertionControl;

    *ExitAssertion = TRUE;

    //
    // Vocalize if so ordered.
    //
    if (assertionControl & VFM_FLAG_BEEP) {

        DbgPrint("%c", 7);
    }

    if (assertionControl & VFM_FLAG_ZAPPED) {

        return;
    }

    //
    // Wait for input...
    //
    waitForInput = TRUE;
    while(waitForInput) {

        if (DcCheckData->Control) {

            DbgPrompt( "Break, Ignore, Zap, Remove, Disable all (bizrd)? ", response, sizeof( response ));
        } else {

            DbgPrompt( "Break, Ignore, Disable all (bid)? ", response, sizeof( response ));
        }

        switch (response[0]) {

            case 'B':
            case 'b':
                DbgPrint("Breaking in... (press g<enter> to return to assert menu)\n");
                DbgBreakPoint();
                waitForInput = FALSE;
                *ExitAssertion = FALSE;
                break;

            case 'I':
            case 'i':
                waitForInput = FALSE;
                break;

            case 'Z':
            case 'z':
                if (DcCheckData->Control) {

                   DbgPrint("Breakpoint zapped (OS will print text and return)\n");
                   assertionControl |= VFM_FLAG_ZAPPED;
                   assertionControl &=~ VFM_FLAG_BEEP;
                   waitForInput = FALSE;
                }
                break;

            case 'D':
            case 'd':
                ViBugCheckControlOverride = VFM_FLAG_CLEARED;
                DbgPrint("Verification asserts disabled.\n");
                waitForInput = FALSE;
                break;

            case 'R':
            case 'r':
                if (DcCheckData->Control) {

                   DbgPrint("Breakpoint removed\n") ;
                   assertionControl |= VFM_FLAG_CLEARED;
                   waitForInput = FALSE;
                }
                break;
        }
    }

    if (DcCheckData->Control) {
        *DcCheckData->Control = assertionControl;
    }
}