// SPDX-License-Identifier: MPL-2.0
//
// Do NOT modify or remove this copyright and license
//
// Copyright (c) 2012-2026 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
//
// This software is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// ******************************************************************************************
//
// \file dst.c
// \brief This file defines the function calls for dst and dst related operations

#include "bit_manip.h"
#include "code_attributes.h"
#include "common_types.h"
#include "error_translation.h"
#include "io_utils.h"
#include "math_utils.h"
#include "memory_safety.h"
#include "precision_timer.h"
#include "sleep.h"
#include "string_utils.h"
#include "time_utils.h"
#include "type_conversion.h"

#include "cmds.h"
#include "dst.h"
#include "logs.h"
#include "operations_Common.h"
#include "platform_helper.h"
#include "sector_repair.h"
#include "smart.h"
#include <stdlib.h>

eReturnValues ata_Abort_DST(const tDevice* device)
{
    return ata_SMART_Offline(device, ATA_SMART_OFFLINE_ABORT_SELF_TEST, DEFAULT_COMMAND_TIMEOUT);
}

eReturnValues scsi_Abort_DST(const tDevice* device)
{
    return scsi_Send_Diagnostic(device, SCSI_STC_ABORT_SELF_TEST, RESERVED, RESERVED, RESERVED, RESERVED, RESERVED,
                                M_NULLPTR, RESERVED, DEFAULT_COMMAND_TIMEOUT);
}

eReturnValues nvme_Abort_DST(const tDevice* device, uint32_t nsid)
{
    return nvme_Device_Self_Test(device, nsid, NVME_STC_ABORT);
}

eReturnValues abort_DST(const tDevice* device)
{
    eReturnValues result = NOT_SUPPORTED;
    switch (device->drive_info.drive_type)
    {
    case NVME_DRIVE:
        result = nvme_Abort_DST(device, UINT32_MAX);
        break;
    case SCSI_DRIVE:
        result = scsi_Abort_DST(device);
        break;
    case ATA_DRIVE:
        result = ata_Abort_DST(device);
        break;
    default:
        break;
    }
    return result;
}

#define ATA_DST_PERCENT_COMPLETE_CONVERSION_FACTOR    UINT32_C(10)
#define ATA_SELF_TEST_PROGRESS_SCSI_CONVERSION_FACTOR UINT32_C(100)
eReturnValues ata_Get_DST_Progress(const tDevice* device, uint32_t* percentComplete, uint8_t* status)
{
    eReturnValues result = SUCCESS;
    DECLARE_ZERO_INIT_ARRAY(uint8_t, temp_buf, ATA_SMART_READ_DATA_SIZE);
    result = ata_SMART_Read_Data(device, temp_buf, sizeof(temp_buf));
    if (result == SUCCESS)
    {
        // get the progress
        *percentComplete = ATA_SELF_TEST_PROGRESS_SCSI_CONVERSION_FACTOR -
                           (M_Nibble0(temp_buf[ATA_SMART_SELF_TEST_EXECUTION_STATUS_OFFSET]) *
                            ATA_DST_PERCENT_COMPLETE_CONVERSION_FACTOR);
        *status = M_Nibble1(temp_buf[ATA_SMART_SELF_TEST_EXECUTION_STATUS_OFFSET]);
    }
    return result;
}

eReturnValues scsi_Get_DST_Progress(const tDevice* device, uint32_t* percentComplete, uint8_t* status)
{
    // 04h 09h LOGICAL UNIT NOT READY, SELF-TEST IN PROGRESS
    eReturnValues result        = UNKNOWN;
    size_t        temp_buf_size = LP_SELF_TEST_RESULTS_LEN;
    uint8_t*      temp_buf      = M_REINTERPRET_CAST(
        uint8_t*, safe_calloc_aligned(temp_buf_size, sizeof(uint8_t), device->os_info.minimumAlignment));
    if (temp_buf == M_NULLPTR)
    {
        perror("Calloc Failure!\n");
        return MEMORY_FAILURE;
    }
    result = scsi_Log_Sense_Cmd(device, false, LPC_CUMULATIVE_VALUES, LP_SELF_TEST_RESULTS, 0, 0, temp_buf,
                                LP_SELF_TEST_RESULTS_LEN);
    if (result == SUCCESS)
    {
        *status = M_Nibble0(temp_buf[8]);
        // check the progress since the test is still running
        safe_memset(temp_buf, temp_buf_size, 0, temp_buf_size);
        scsi_Request_Sense_Cmd(device, false, temp_buf, SPC3_SENSE_LEN);
        *percentComplete = get_SCSI_Progress_Indicator_Percent(M_BytesTo2ByteValue(temp_buf[16], temp_buf[17]));
    }
    safe_free_aligned(&temp_buf);
    return result;
}

eReturnValues nvme_Get_DST_Progress(const tDevice* device, uint32_t* percentComplete, uint8_t* status)
{
    eReturnValues result = UNKNOWN;
    DECLARE_ZERO_INIT_ARRAY(uint8_t, nvmeSelfTestLogBuf, NVME_LOG_SIZE_SELF_TEST);
    nvmeGetLogPageCmdOpts getDSTLog;
    safe_memset(&getDSTLog, sizeof(nvmeGetLogPageCmdOpts), 0, sizeof(nvmeGetLogPageCmdOpts));
    getDSTLog.addr    = nvmeSelfTestLogBuf;
    getDSTLog.dataLen = NVME_LOG_SIZE_SELF_TEST;
    getDSTLog.lid     = NVME_LOG_DEV_SELF_TEST_ID;
    if (SUCCESS == nvme_Get_Log_Page(device, &getDSTLog))
    {
        result = SUCCESS;
        if (nvmeSelfTestLogBuf[0] == 0)
        {
            // no self test in progress
            *percentComplete = 0;
            // need to set a status value based on the most recent result data
            uint32_t newestResultOffset = UINT32_C(4);
            *status                     = M_Nibble0(nvmeSelfTestLogBuf[newestResultOffset +
                                                   0]); // This should be fine for the rest of the running DST code.
            // According to spec, if status bit is 0x0F, that means entry is not valid(doesn't contain valid test
            // results. and in that case, we have to ignore this bit, and consider this as completed/success. I have
            // seen this issue on the drive, where DST was never run. - Nidhi
            if (*status == 0x0F)
            {
                *status = 0;
            }
        }
        else
        {
            // NVMe made this simple...if this is 25, then it's 25% complete. no silly business
            *percentComplete = nvmeSelfTestLogBuf[1];
            // Setting the status to Fh to work with existing SCSI/ATA code in the run_DST_Function - TJE
            *status = 0x0F;
        }
    }
    else
    {
        result = FAILURE;
    }
    return result;
}

eReturnValues get_DST_Progress(const tDevice* device, uint32_t* percentComplete, uint8_t* status)
{
    eReturnValues result = UNKNOWN;
    *percentComplete     = 0;
    *status              = 0xFF;
    switch (device->drive_info.drive_type)
    {
    case ATA_DRIVE:
        result = ata_Get_DST_Progress(device, percentComplete, status);
        break;
    case NVME_DRIVE:
        result = nvme_Get_DST_Progress(device, percentComplete, status);
        break;
    case SCSI_DRIVE:
        result = scsi_Get_DST_Progress(device, percentComplete, status);
        break;
    default:
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            print_str("Not supported on this device type at this time");
        }
        return NOT_SUPPORTED;
    }
    return result;
}

void translate_DST_Status_To_String(uint8_t status, char* translatedString, bool justRanDST, bool isNVMeDrive)
{

    if (translatedString != M_NULLPTR)
    {
        safe_memset(translatedString, MAX_DST_STATUS_STRING_LENGTH, 0, MAX_DST_STATUS_STRING_LENGTH);
        if (isNVMeDrive)
        {
            switch (status)
            {
            case 0x00:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Operation completed without error.");
                break;
            case 0x01:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Operation was aborted by a Device Self-test command.");
                break;
            case 0x02:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Operation was aborted by a Controller Level Reset.");
                break;
            case 0x03:
                snprintf_err_handle(
                    translatedString, MAX_DST_STATUS_STRING_LENGTH,
                    "Operation was aborted due to a removal of a namespace from the namespace inventory.");
                break;
            case 0x04:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Operation was aborted due to the processing of a Format NVM command.");
                break;
            case 0x05:
                snprintf_err_handle(
                    translatedString, MAX_DST_STATUS_STRING_LENGTH,
                    "A fatal error or unknown test error occurred while the controller was executing the device "
                    "self-test operation and the operation did not complete.");
                break;
            case 0x06:
                snprintf_err_handle(
                    translatedString, MAX_DST_STATUS_STRING_LENGTH,
                    "Operation completed with a segment that failed and the segment that failed is not known.");
                break;
            case 0x07:
                snprintf_err_handle(
                    translatedString, MAX_DST_STATUS_STRING_LENGTH,
                    "Operation completed with one or more failed segments and the first segment that failed is "
                    "indicated in the Segment Number field.");
                break;
            case 0x08:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Operation was aborted for unknown reason.");
                break;
            case 0x0F: // NOTE: The spec says that this is NOT used. We are dummying this up to work with existing
                       // SAS/SATA code which is why this is here - TJE
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH, "Operation in progress.");
                break;
            default:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Error, unknown status: %" PRIX8 "h.", status);
                break;
            }
        }
        else
        {
            switch (status)
            {
            case 0x00:
                if (justRanDST)
                {
                    snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                        "The self-test routine completed without error.");
                }
                else
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The previous self-test routine completed without error or no self-test has ever been run.");
                }
                break;
            case 0x01:

                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "The self-test routine was aborted by the host.");
                break;
            case 0x02:
                snprintf_err_handle(
                    translatedString, MAX_DST_STATUS_STRING_LENGTH,
                    "The self-test routine was interrupted by the host with a hardware or software reset.");
                break;
            case 0x03:
                snprintf_err_handle(
                    translatedString, MAX_DST_STATUS_STRING_LENGTH,
                    "A fatal error or unknown test error occurred while the device was executing its self-test "
                    "routine and the device was unable to complete the self-test routine.");
                break;
            case 0x04:
                if (justRanDST)
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The self-test completed having a test element that failed and the test element that failed "
                        "is not known.");
                }
                else
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The previous self-test completed having a test element that failed and the test element that "
                        "failed is not known.");
                }
                break;
            case 0x05:
                if (justRanDST)
                {
                    snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                        "The self-test completed having the electrical element of the test failed.");
                }
                else
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The previous self-test completed having the electrical element of the test failed.");
                }
                break;
            case 0x06:
                if (justRanDST)
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The self-test completed having the servo (and/or seek) test element of the test failed.");
                }
                else
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The previous self-test completed having the servo (and/or seek) test element of the test "
                        "failed.");
                }
                break;
            case 0x07:
                if (justRanDST)
                {
                    snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                        "The self-test completed having the read element of the test failed.");
                }
                else
                {
                    snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                        "The previous self-test completed having the read element of the test failed.");
                }
                break;
            case 0x08:
                if (justRanDST)
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The self-test completed having a test element that failed and the device is suspected of "
                        "having handling damage.");
                }
                else
                {
                    snprintf_err_handle(
                        translatedString, MAX_DST_STATUS_STRING_LENGTH,
                        "The previous self-test completed having a test element that failed and the device is "
                        "suspected of having handling damage.");
                }
                break;
            case 0x09:
            case 0x0A:
            case 0x0B:
            case 0x0C:
            case 0x0D:
            case 0x0E:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH, "Reserved Status.");
                break;
            case 0x0F:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH, "Self-test in progress.");
                break;
            default:
                snprintf_err_handle(translatedString, MAX_DST_STATUS_STRING_LENGTH,
                                    "Error, unknown status: %" PRIX8 "h.", status);
            }
        }
    }
}

eReturnValues print_DST_Progress(const tDevice* device)
{
    eReturnValues result          = UNKNOWN;
    uint32_t      percentComplete = UINT32_C(0);
    uint8_t       status          = UINT8_C(0xFF);
    result                        = get_DST_Progress(device, &percentComplete, &status);
    if (result == NOT_SUPPORTED)
    {
        return result;
    }
    else if (result != SUCCESS)
    {
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            print_str("An error occurred while trying to retrieve DST Progress\n");
        }
    }
    else
    {
        bool isNVMeDrive = false;
        DECLARE_ZERO_INIT_ARRAY(char, statusTranslation, MAX_DST_STATUS_STRING_LENGTH);
        if (device->drive_info.drive_type == NVME_DRIVE)
        {
            isNVMeDrive = true;
        }
        printf("\tTest Progress = %" PRIu32 "%%\n", percentComplete);
        translate_DST_Status_To_String(status, statusTranslation, false, isNVMeDrive);
        printf("%s\n", statusTranslation);
        switch (status)
        {
        case 0x00:
            result = SUCCESS;
            break;
        case 0x01:
        case 0x02:
            result = ABORTED;
            break;
        case 0x03:
        case 0x04:
        case 0x05:
        case 0x06:
        case 0x07:
        case 0x08:
            result = FAILURE;
            break;
        case 0x09:
        case 0x0A:
        case 0x0B:
        case 0x0C:
        case 0x0D:
        case 0x0E:
            break;
        case 0x0F:
            result = IN_PROGRESS;
            break;
        default:
            result = FAILURE;
            break;
        }
    }
    return result;
}

M_PARAM_RO(1) static bool is_NVME_Self_Test_Supported(const tDevice* M_NONNULL device)
{
    bool supported = false;
    // set based on controller reported capabilities first
    if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.oacs) & BIT4)
    {
        supported = true;
    }
    // check if there is a passthrough limitation next
    if (device->drive_info.passThroughHacks.nvmePTHacks.limitedPassthroughCapabilities &&
        !device->drive_info.passThroughHacks.nvmePTHacks.limitedCommandsSupported.deviceSelfTest)
    {
        supported = false;
    }
    return supported;
}

M_PARAM_RO(1) static bool is_SCSI_Self_Test_Supported(const tDevice* M_NONNULL device)
{
    bool supported = false;
    DECLARE_ZERO_INIT_ARRAY(uint8_t, selfTestResultsLog, LP_SELF_TEST_RESULTS_LEN);
    if (SUCCESS == scsi_Log_Sense_Cmd(device, false, LPC_CUMULATIVE_VALUES, LP_SELF_TEST_RESULTS, 0, 0,
                                      selfTestResultsLog, LP_SELF_TEST_RESULTS_LEN))
    {
        supported = true;
    }
    return supported;
}

M_PARAM_RO(1) static bool is_ATA_Self_Test_Supported(const tDevice* M_NONNULL device)
{
    bool supported = false;
    if (is_SMART_Enabled(device))
    {
        // also check that self test is supported by the drive
        if ((is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word084)) &&
             le16_to_host(device->drive_info.IdentifyData.ata.Word084) & BIT1) ||
            (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word087)) &&
             le16_to_host(device->drive_info.IdentifyData.ata.Word087) & BIT1))
        {
            // NOTE: Also need to check the SMART read data as it also contains a bit to indicate if DST is
            // supported or not!
            //       That field also indicates whether Short, extended, or conveyance tests are supported!
            //       Since the SMART read data has been made obsolete on newer standards, we may need a version
            //       check or something to keep proper behavior as new devices show up without support for this
            //       information.
            // SMART read data is listed as optional in ata/atapi-7
            DECLARE_ZERO_INIT_ARRAY(uint8_t, smartData, ATA_SMART_READ_DATA_SIZE);
            if (SUCCESS == ata_SMART_Read_Data(device, smartData, ATA_SMART_READ_DATA_SIZE))
            {
                // check the pff-line data collection capability field
                // assume this is more accurate since this seems to be the case with some older products
                if ((smartData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT0) &&
                    (smartData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] &
                     BIT4)) // bit0 = the subcommand, bit4 = self-test routine implemented (short and extended)
                {
                    supported = true;
                }
            }
            else
            {
                // assume that the identify bits were accurate for this command.
                supported = true;
            }
        }
    }
    return supported;
}

bool is_Self_Test_Supported(const tDevice* device)
{
    bool supported = false;
    switch (device->drive_info.drive_type)
    {
    case NVME_DRIVE:
        supported = is_NVME_Self_Test_Supported(device);
        break;
    case SCSI_DRIVE:
        supported = is_SCSI_Self_Test_Supported(device);
        break;
    case ATA_DRIVE:
        supported = is_ATA_Self_Test_Supported(device);
        break;
    default:
        break;
    }
    return supported;
}

bool is_Conveyance_Self_Test_Supported(const tDevice* device)
{
    bool supported = false;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, smartReadData, ATA_SMART_READ_DATA_SIZE);
        if (SUCCESS == ata_SMART_Read_Data(device, smartReadData, ATA_SMART_READ_DATA_SIZE))
        {
            if ((smartReadData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT0) &&
                (smartReadData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT5))
            {
                supported = true;
            }
        }
    }
    return supported;
}

// Incorrect spelling so reroute to correct spelling
bool is_Conveyence_Self_Test_Supported(const tDevice* device)
{
    return is_Conveyance_Self_Test_Supported(device);
}

bool is_Selective_Self_Test_Supported(const tDevice* device)
{
    bool supported = false;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, smartReadData, ATA_SMART_READ_DATA_SIZE);
        if (SUCCESS == ata_SMART_Read_Data(device, smartReadData, ATA_SMART_READ_DATA_SIZE))
        {
            if ((smartReadData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT0) &&
                (smartReadData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT6))
            {
                supported = true;
            }
        }
    }
    return supported;
}

eReturnValues send_DST(const tDevice* device, eDSTType DSTType, bool captiveForeground, uint32_t commandTimeout)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (commandTimeout == 0)
    {
        if (os_Is_Infinite_Timeout_Supported())
        {
            commandTimeout = INFINITE_TIMEOUT_VALUE;
        }
        else
        {
            commandTimeout = MAX_CMD_TIMEOUT_SECONDS;
        }
    }
    os_Lock_Device(device);
    switch (device->drive_info.drive_type)
    {
    case NVME_DRIVE:
        switch (DSTType)
        {
        case DST_TYPE_SHORT:
            ret = nvme_Device_Self_Test(device, UINT32_MAX, NVME_STC_SHORT);
            break;
        case DST_TYPE_LONG:
            ret = nvme_Device_Self_Test(device, UINT32_MAX, NVME_STC_EXTENDED);
            break;
        case DST_TYPE_CONVEYANCE: // not available on NVMe
        default:
            break;
        }
        break;
    case SCSI_DRIVE:
        switch (DSTType)
        {
        case DST_TYPE_SHORT:
            if (captiveForeground)
            {
                ret = scsi_Send_Diagnostic(device, SCSI_STC_FOREGROUND_SHORT_SELF_TEST, RESERVED, RESERVED, RESERVED,
                                           RESERVED, RESERVED, M_NULLPTR, RESERVED, commandTimeout);
            }
            else
            {
                ret = scsi_Send_Diagnostic(device, SCSI_STC_BACKGROUND_SHORT_SELF_TEST, RESERVED, RESERVED, RESERVED,
                                           RESERVED, RESERVED, M_NULLPTR, RESERVED, commandTimeout);
            }
            break;
        case DST_TYPE_LONG:
            if (captiveForeground)
            {
                ret = scsi_Send_Diagnostic(device, SCSI_STC_FOREGROUND_EXTENDED_SELF_TEST, RESERVED, RESERVED, RESERVED,
                                           RESERVED, RESERVED, M_NULLPTR, RESERVED, commandTimeout);
            }
            else
            {
                ret = scsi_Send_Diagnostic(device, SCSI_STC_BACKGROUND_EXTENDED_SELF_TEST, RESERVED, RESERVED, RESERVED,
                                           RESERVED, RESERVED, M_NULLPTR, RESERVED, commandTimeout);
            }
            break;
        case DST_TYPE_CONVEYANCE: // not available on SCSI
        default:
            break;
        }
        break;
    case ATA_DRIVE:
        switch (DSTType)
        {
        case DST_TYPE_SHORT:
            if (captiveForeground)
            {
                ret = ata_SMART_Offline(device, ATA_SMART_OFFLINE_CAPTIVE_SHORT_SELF_TEST, commandTimeout);
            }
            else
            {
                ret = ata_SMART_Offline(device, ATA_SMART_OFFLINE_SHORT_SELF_TEST, commandTimeout);
            }
            break;
        case DST_TYPE_LONG:
            if (captiveForeground)
            {
                ret = ata_SMART_Offline(device, ATA_SMART_OFFLINE_CAPTIVE_EXTENDED_SELF_TEST, commandTimeout);
            }
            else
            {
                ret = ata_SMART_Offline(device, ATA_SMART_OFFLINE_EXTENDED_SELF_TEST, commandTimeout);
            }
            break;
        case DST_TYPE_CONVEYANCE:
            if (is_Conveyance_Self_Test_Supported(device))
            {
                if (captiveForeground)
                {
                    ret = ata_SMART_Offline(device, ATA_SMART_OFFLINE_CAPTIVE_CONVEYANCE_SELF_TEST, commandTimeout);
                }
                else
                {
                    ret = ata_SMART_Offline(device, ATA_SMART_OFFLINE_CONVEYANCE_SELF_TEST, commandTimeout);
                }
            }
            else
            {
                ret = NOT_SUPPORTED;
            }
            break;
        default:
            break;
        }
        break;
    default:
        ret = NOT_SUPPORTED;
        break;
    }
    os_Unlock_Device(device);
    return ret;
}

M_PARAM_RO(1)
static bool is_ATA_SMART_Offline_Supported(const tDevice* M_NONNULL device,
                                           bool* M_NULLABLE         abortRestart,
                                           uint16_t* M_NULLABLE     offlineTimeSeconds)
{
    bool supported = false;
    if (is_SMART_Enabled(device))
    {
        // also check that self test is supported by the drive
        if ((is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word084)) &&
             le16_to_host(device->drive_info.IdentifyData.ata.Word084) & BIT1) ||
            (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word087)) &&
             le16_to_host(device->drive_info.IdentifyData.ata.Word087) & BIT1))
        {
            // NOTE: Also need to check the SMART read data as it also contains a bit to indicate if DST is supported or
            // not!
            //       That field also indicates whether Short, extended, or conveyance tests are supported!
            //       Since the SMART read data has been made obsolete on newer standards, we may need a version check
            //       or something to keep proper behavior as new devices show up without support for this information.
            // SMART read data is listed as optional in ata/atapi-7
            DECLARE_ZERO_INIT_ARRAY(uint8_t, smartData, ATA_SMART_READ_DATA_SIZE);
            if (SUCCESS == ata_SMART_Read_Data(device, smartData, ATA_SMART_READ_DATA_SIZE))
            {
                // check the pff-line data collection capability field
                // assume this is more accurate since this seems to be the case with some older products
                if (smartData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT0) // bit0 = the subcommand
                {
                    supported = true;
                    if (abortRestart != M_NULLPTR)
                    {
                        if (smartData[ATA_SMART_OFFLINE_DATA_COLLECTION_CAPABILITY_OFFSET] & BIT2)
                        {
                            *abortRestart = true;
                        }
                        else
                        {
                            *abortRestart = false;
                        }
                    }
                    if (offlineTimeSeconds != M_NULLPTR)
                    {
                        *offlineTimeSeconds = M_BytesTo2ByteValue(
                            smartData[ATA_SMART_OFFLINE_DATA_COLLECTION_POLLING_TIME_WORD_OFFSET + 1],
                            smartData[ATA_SMART_OFFLINE_DATA_COLLECTION_POLLING_TIME_WORD_OFFSET]);
                    }
                }
            }
            else
            {
                // assume that the identify bits were accurate for this command.
                supported = true;
            }
        }
    }
    return supported;
}

M_PARAM_RO(1)
M_PARAM_WO(2) static eReturnValues get_SMART_Offline_Status(const tDevice* M_NONNULL device, uint8_t* M_NONNULL status)
{
    eReturnValues ret = SUCCESS;

    if (status == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    DECLARE_ZERO_INIT_ARRAY(uint8_t, smartData, ATA_SMART_READ_DATA_SIZE);
    ret = ata_SMART_Read_Data(device, smartData, ATA_SMART_READ_DATA_SIZE);
    if (ret == SUCCESS)
    {
        *status = smartData[ATA_SMART_OFFLINE_COLLECTION_STATUS_OFFSET];
    }
    return ret;
}

// NOTE: During my testing, even a drive that doesn't "abort" due to a command interruption never continues when trying
// to poll.
//       But the "in progress" wasn't added until ATA/ATAPI-7, so the polling may need to be restricted to drives with
//       that standard compliance. The drive I have been testing is older and does not support that and does not seem to
//       ever restart on its own. The standards just say it restarts after a "vendor specific event". Because of this,
//       the polling code is removed entirely unless the following #define is set to reenable it. -TJE
// #define ENABLE_SMART_OFFLINE_ROUTINE_POLLING 1
eReturnValues run_SMART_Offline(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        bool     abortRestart         = false;
        uint16_t offlineTimeInSeconds = UINT16_C(0);
        if (is_ATA_SMART_Offline_Supported(device, &abortRestart, &offlineTimeInSeconds))
        {
            uint8_t hours   = UINT8_C(0);
            uint8_t minutes = UINT8_C(0);
            uint8_t seconds = UINT8_C(0);
            convert_Seconds_To_Displayable_Time(offlineTimeInSeconds, M_NULLPTR, M_NULLPTR, &hours, &minutes, &seconds);
            printf("Data Collection time: %2" PRIu8 " hours, %2" PRIu8 " minutes, %2" PRIu8 " seconds\n", hours,
                   minutes, seconds);
            if (abortRestart)
            {
                print_str("\tInterrupting commands will cause data collection to abort and will require manually "
                          "restarting.\n");
            }
            else
            {
                print_str(
                    "\tInterrupting commands will cause data collection to suspend and will restart after a vendor "
                    "specific event.\n");
            }
            time_t currentTime = time(M_NULLPTR);
            time_t futureTime  = get_Future_Date_And_Time(currentTime, offlineTimeInSeconds);
            DECLARE_ZERO_INIT_ARRAY(char, timeFormat, TIME_STRING_LENGTH);
            printf("\tEstimated completion Time : sometime after %s\n",
                   get_Current_Time_String(C_CAST(const time_t*, &futureTime), timeFormat, TIME_STRING_LENGTH));
            ret = ata_SMART_Offline(device, 0, DEFAULT_COMMAND_TIMEOUT);
            if (ret == SUCCESS)
            {
                uint8_t status = UINT8_C(0);
                // count down for the abount of time the drive reports for how long this should take
                uint16_t countDownSecondsRemaining = offlineTimeInSeconds;
#if defined ENABLE_SMART_OFFLINE_ROUTINE_POLLING
                bool     inProgress = true;
                uint16_t pollingTime =
                    offlineTimeInSeconds / 10; // attempting to check enough times that it's in 10% increments, even
                                               // though the device will not tell us a real percent complete here.
                uint16_t pollCounter = UINT16_C(0);
#endif // ENABLE_SMART_OFFLINE_ROUTINE_POLLING
                while (countDownSecondsRemaining > 0
#if defined ENABLE_SMART_OFFLINE_ROUTINE_POLLING
                       && inProgress
#endif // ENABLE_SMART_OFFLINE_ROUTINE_POLLING
                )
                {
                    convert_Seconds_To_Displayable_Time(countDownSecondsRemaining, M_NULLPTR, M_NULLPTR, &hours,
                                                        &minutes, &seconds);
                    printf("\r%2" PRIu8 " hours, %2" PRIu8 " minutes, %2" PRIu8 " seconds remaining", hours, minutes,
                           seconds);
                    flush_stdout();
                    delay_Seconds(1);
                    --countDownSecondsRemaining;
#if defined ENABLE_SMART_OFFLINE_ROUTINE_POLLING
                    ++pollCounter;
                    if (!abortRestart && pollCounter == pollingTime)
                    {
                        // time to to a progress check to determine if we should keep waiting or exit early.
                        if (SUCCESS == get_SMART_Offline_Status(device, &status))
                        {
                            switch (status)
                            {
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_NEVER_STARTED:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_NEVER_STARTED_80:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_COMPLETED_NO_ERRORS:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_COMPLETED_NO_ERRORS_82:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_HOST_COMMAND:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_HOST_COMMAND_85:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_DEVICE:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_DEVICE_86:
                                inProgress = false;
                                break;
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_SUSPENDED_BY_HOST_COMMAND:
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_SUSPENDED_BY_HOST_COMMAND_84:
                                // command "suspended" can be returned just by making the query to get progress.
                                // So treat this case as "in progress" as well and do not stop.
                                // SEE NOTE above. This does not seem to work like expected as this never seems to
                                // restart -TJE
                                inProgress = false;
                                break;
                            case ATA_SMART_OFFLINE_COLLECT_STATUS_IN_PROGRESS:
                                // still in progress. No need to stop.
                                break;
                            default:
                                // reserved or vendor unique status....leave as "in progress" until we get one of the
                                // completion codes. Leaving this as "in progress" until the time runs out because some
                                // early drives may have used a vendor unique code to indicate "in progress" versus some
                                // other error since the case "3" above was not defined until later.-TJE
                                break;
                            }
                        }
                        // else just let it keep running for the time the drive reported earlier.
                        pollCounter = 0;
                    }
#endif // ENABLE_SMART_OFFLINE_ROUTINE_POLLING
                }
                // print this to finish the countdown to zero.-TJE
                print_str("\r 0 hours,  0 minutes,  0 seconds remaining\n");
                if (SUCCESS == get_SMART_Offline_Status(device, &status))
                {
                    print_str("\nSMART Off-line data collection ");
                    switch (status)
                    {
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_NEVER_STARTED:
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_NEVER_STARTED_80:
                        print_str("never started\n");
                        break;
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_COMPLETED_NO_ERRORS:
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_COMPLETED_NO_ERRORS_82:
                        print_str("completed without error\n");
                        break;
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_SUSPENDED_BY_HOST_COMMAND:
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_SUSPENDED_BY_HOST_COMMAND_84:
                        print_str("was suspended by an interrupting command from the host\n");
                        break;
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_HOST_COMMAND:
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_HOST_COMMAND_85:
                        print_str("was aborted by an interrupting command from the host\n");
                        break;
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_DEVICE:
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_ABORTED_BY_DEVICE_86:
                        print_str("was aborted by the device with a fatal error\n");
                        break;
                    case ATA_SMART_OFFLINE_COLLECT_STATUS_IN_PROGRESS:
                        print_str("is progress\n");
                        break;
                    default:
                        if (status >= ATA_SMART_OFFLINE_COLLECT_STATUS_VENDOR_SPECIFIC_C0 /*through 0xff*/ ||
                            (status >= ATA_SMART_OFFLINE_COLLECT_STATUS_VENDOR_SPECIFIC_40 &&
                             status <= ATA_SMART_OFFLINE_COLLECT_STATUS_VENDOR_SPECIFIC_7F))
                        {
                            print_str("status is vendor specific\n");
                        }
                        else
                        {
                            print_str("status is reserved\n");
                        }
                        break;
                    }
                }
            }
        }
    }
    return ret;
}

typedef struct s_DSTTiming
{
    uint32_t pollingInterval;
    uint32_t commandTimeout;
    uint32_t maxTimeIncreases;
    uint32_t timeIncreaseWarningCount;
    uint32_t timeDiff;
    uint32_t totalDSTTimeSeconds;
    uint32_t maxDSTWaitTimeSeconds;
} dstTiming;

static dstTiming get_DST_Polling_Interval(const tDevice* M_NONNULL device, eDSTType DSTType, bool captiveForeground)
{
    dstTiming timing;
    safe_memset(&timing, sizeof(dstTiming), 0, sizeof(dstTiming));
    // time to start the DST
    timing.pollingInterval          = UINT32_C(5);             // assume 5 second delay between progress checks
    timing.commandTimeout           = DEFAULT_COMMAND_TIMEOUT; // start with this default timeout value - TJE
    timing.timeDiff                 = UINT32_C(30);
    timing.maxTimeIncreases         = UINT32_C(2);
    timing.timeIncreaseWarningCount = UINT32_C(1);
    timing.totalDSTTimeSeconds      = UINT32_C(120);
    timing.maxDSTWaitTimeSeconds    = timing.totalDSTTimeSeconds * 5;
    switch (DSTType)
    {
    case DST_TYPE_SHORT: // short
        timing.pollingInterval = 5;
        if (captiveForeground)
        {
            timing.commandTimeout = 120; // two minutes as per ATA and SCSI specifications
        }
        break;
    case DST_TYPE_LONG: // extended
        // Since long DST is slower in terms of when progress is achieved, we need to change some things such as
        // how often we poll, how often we expect progress increments, and the maximum number of times to allow
        // for increasing the polling interval. For now, I'm just multiplying everything by 3. Not sure if there
        // is a better way to do this or not.
        timing.pollingInterval *= 3;
        timing.timeDiff *= 3;
        timing.maxTimeIncreases *= 3;
        timing.timeIncreaseWarningCount *= 3;
        {
            uint8_t hours   = UINT8_C(0);
            uint8_t minutes = UINT8_C(0);
            if (SUCCESS == get_Long_DST_Time(device, &hours, &minutes))
            {
                timing.totalDSTTimeSeconds =
                    (C_CAST(uint32_t, hours) * UINT32_C(3600)) + (C_CAST(uint32_t, minutes) * UINT32_C(60));
                if (captiveForeground)
                {
                    timing.commandTimeout = timing.totalDSTTimeSeconds;
                }
                timing.maxDSTWaitTimeSeconds = timing.totalDSTTimeSeconds * UINT32_C(5);
            }
            else
            {
                if (captiveForeground)
                {
                    // set a maximum time out value
                    timing.commandTimeout = UINT32_MAX;
                }
                timing.totalDSTTimeSeconds = 14400; // a fallback for drives not reporting a time, so using 4 hours for
                                                    // now. This likely will not ever happen
                timing.maxDSTWaitTimeSeconds = timing.totalDSTTimeSeconds * 5;
            }
        }
        break;
    case DST_TYPE_CONVEYANCE: // conveyance
        timing.pollingInterval = 5;
        if (captiveForeground)
        {
            timing.commandTimeout = 120; // two minutes as per ATA spec
        }
        break;
    }
    return timing;
}

M_PARAM_RO(1)
static eReturnValues poll_DST_Progress(const tDevice* M_NONNULL device,
                                       dstTiming                timing,
                                       bool                     ignoreMaxTime,
                                       bool                     showProgress)
{
    eReturnValues ret = IN_PROGRESS;
    delay_Seconds(1); // delay for a second before starting to poll for progress to give it time to start
    // set status to 0x08 before the loop or it will not get entered
    uint8_t     status                 = 0x0F;
    uint32_t    percentComplete        = UINT32_C(0);
    time_t      dstProgressTimer       = time(M_NULLPTR);
    time_t      startTime              = time(M_NULLPTR);
    uint32_t    lastProgressIndication = UINT32_C(0);
    uint8_t     timeExtensionCount     = UINT8_C(0);
    const char* overTimeWarningMessage = "WARNING: DST is taking longer than expected.";
    bool        showTimeWarning        = false;
    bool        abortForTooLong        = false;
    while (status == 0x0F && (ret == SUCCESS || ret == IN_PROGRESS))
    {
        lastProgressIndication = percentComplete;
        ret                    = get_DST_Progress(device, &percentComplete, &status);
        if (VERBOSITY_QUIET < device->deviceVerbosity && showProgress)
        {
            if (showTimeWarning)
            {
                printf("\r    Test progress: %" PRIu32 "%% complete. %s", percentComplete, overTimeWarningMessage);
            }
            else
            {
                printf("\r    Test progress: %" PRIu32 "%% complete.  ", percentComplete);
            }
            if (status != 0x00)
            {
                flush_stdout();
            }
        }
        if (status < 0x08) // known errors are less than this value
        {
            break;
        }
        // make the time between progress polls bigger if it isn't changing quickly to allow the drive time
        // to finish any recovery it's doing.
        if (difftime(time(M_NULLPTR), dstProgressTimer) > timing.timeDiff && lastProgressIndication == percentComplete)
        {
            // We are likely pinging the drive too quickly during the read test and error recovery isn't
            // finishing...extend the delay time
            if (timeExtensionCount <= timing.maxTimeIncreases)
            {
                timing.pollingInterval *= 2;
                timing.timeDiff *= 2;
                ++timeExtensionCount;
            }
            dstProgressTimer = time(M_NULLPTR); // reset this beginning timer since we changed the polling time
            if (!ignoreMaxTime && timeExtensionCount > timing.maxTimeIncreases &&
                difftime(dstProgressTimer, startTime) > timing.maxDSTWaitTimeSeconds)
            {
                // only abort if we are past the total DST time and have already increased the delays
                // multiple times. we've extended the polling time too much. Something else is wrong in the
                // drive. Just abort it and exit.
                ret             = abort_DST(device);
                ret             = ABORTED;
                abortForTooLong = true;
                break;
            }
            else if (timeExtensionCount > timing.timeIncreaseWarningCount &&
                     difftime(dstProgressTimer, startTime) > timing.totalDSTTimeSeconds)
            {
                showTimeWarning = true;
            }
        }
        delay_Seconds(timing.pollingInterval);
    }
    if (status == 0 && ret == SUCCESS)
    {
        if (showProgress)
        {
            // printf 35 characters + width of warning message to clear the line before printing this final
            // status update
            printf("\r                                    %.*s", C_CAST(int, safe_strlen(overTimeWarningMessage)),
                   "                                                                        ");
            print_str("\r    Test progress: 100% complete   ");
            flush_stdout();
        }
    }
    else if (status == 0x01 || status == 0x02 || ret == ABORTED)
    {
        // DST was aborted by the host with either a reset or a abort command
        ret = ABORTED;
    }
    else if (ret != ABORTED)
    {
        ret = FAILURE; // failed the test
    }
    if (VERBOSITY_QUIET < device->deviceVerbosity && showProgress)
    {
        if (abortForTooLong)
        {
            print_str("\nDST was aborted for taking too long. This may happen if other disc activity\n");
            print_str("is too high! Please check to make sure no other disk IO is occurring so that DST\n");
            print_str("can complete as expected!\n");
        }
        else
        {
            bool isNVMeDrive = false;
            DECLARE_ZERO_INIT_ARRAY(char, statusTranslation, MAX_DST_STATUS_STRING_LENGTH);
            if (device->drive_info.drive_type == NVME_DRIVE)
            {
                isNVMeDrive = true;
            }
            translate_DST_Status_To_String(status, statusTranslation, true, isNVMeDrive);
            printf("\n%s\n", statusTranslation);
        }
    }
    return ret;
}

eReturnValues run_DST(const tDevice* device,
                      eDSTType       DSTType,
                      bool           pollForProgress,
                      bool           captiveForeground,
                      bool           ignoreMaxTime)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (is_Self_Test_Supported(device))
    {
        uint8_t   status          = UINT8_C(0xF0);
        uint32_t  percentComplete = UINT32_C(0);
        dstTiming timing          = get_DST_Polling_Interval(device, DSTType, captiveForeground);
        // check if DST is already running
        ret = get_DST_Progress(device, &percentComplete, &status);
        if (status == 0x0F)
        {
            ret = poll_DST_Progress(device, timing, ignoreMaxTime, true);
        }
        else if (ret == SUCCESS)
        {
            if (ret == SUCCESS)
            {
                ret = send_DST(device, DSTType, captiveForeground, timing.commandTimeout);
            }
            // now poll for progress if it was requested
            if ((ret == SUCCESS || ret == IN_PROGRESS) && pollForProgress && !captiveForeground)
            {
                ret = poll_DST_Progress(device, timing, ignoreMaxTime, true);
            }
            else if (ret == SUCCESS && captiveForeground)
            {
                // need to check the result! Probably best to do this from DST log in case ATA RTFRs are not reported
                // back correctly. - TJE
                if (device->drive_info.drive_type == NVME_DRIVE)
                {
                    // simulate a "captive" test on NVMe by polling and waiting to get status.
                    while (status == 0x0F && ret == SUCCESS)
                    {
                        ret = get_DST_Progress(device, &percentComplete, &status);
                    }
                    if (status == 0)
                    {
                        ret = SUCCESS;
                    }
                    else
                    {
                        ret = FAILURE;
                    }
                }
                else
                {
                    // if the LBA registers have C2-4F or 2C-F4, then we have pass vs fail results.
                    if (device->drive_info.drive_type == ATA_DRIVE &&
                        device->drive_info.lastCommandRTFRs.lbaMid == ATA_SMART_SIG_MID &&
                        device->drive_info.lastCommandRTFRs.lbaHi == ATA_SMART_SIG_HI)
                    {
                        ret = SUCCESS;
                    }
                    else if (device->drive_info.drive_type == ATA_DRIVE &&
                             device->drive_info.lastCommandRTFRs.lbaMid == ATA_SMART_BAD_SIG_MID &&
                             device->drive_info.lastCommandRTFRs.lbaHi == ATA_SMART_BAD_SIG_HI)
                    {
                        // SMART is tripped
                        ret = FAILURE;
                    }
                    else // SCSI drive, or ATA rtfrs not available
                    {
                        // otherwise, read the DST log to figure out the results
                        dstLogEntries logEntries;
                        safe_memset(&logEntries, sizeof(dstLogEntries), 0, sizeof(dstLogEntries));
                        // read the DST log for the result to avoid any SATL issues...
                        if (SUCCESS == get_DST_Log_Entries(device, &logEntries))
                        {
                            if (0 == M_Nibble1(logEntries.dstEntry[0].selfTestExecutionStatus))
                            {
                                ret = SUCCESS;
                            }
                            else if (0xF == M_Nibble1(logEntries.dstEntry[0].selfTestExecutionStatus))
                            {
                                // NOTE: this shouldn't ever happen, but I've seen weird things before...-TJE
                                ret = IN_PROGRESS;
                            }
                            else if (0x1 == M_Nibble1(logEntries.dstEntry[0].selfTestExecutionStatus) ||
                                     0x2 == M_Nibble1(logEntries.dstEntry[0].selfTestExecutionStatus))
                            {
                                // DST was aborted by the host somehow.
                                ret = ABORTED;
                            }
                            else
                            {
                                ret = FAILURE;
                            }
                        }
                        else
                        {
                            ret = UNKNOWN;
                        }
                    }
                }
                if (VERBOSITY_QUIET < device->deviceVerbosity)
                {
                    bool isNVMeDrive = false;
                    DECLARE_ZERO_INIT_ARRAY(char, statusTranslation, MAX_DST_STATUS_STRING_LENGTH);
                    if (device->drive_info.drive_type == NVME_DRIVE)
                    {
                        isNVMeDrive = true;
                    }
                    translate_DST_Status_To_String(status, statusTranslation, true, isNVMeDrive);
                    printf("\n%s\n", statusTranslation);
                }
            }
        }
    }
    else
    {
        ret = NOT_SUPPORTED;
    }
    return ret;
}

M_NONNULL_PARAM_LIST(1, 2, 3)
M_PARAM_RO(1)
M_PARAM_WO(2)
M_PARAM_WO(3)
static eReturnValues get_ATA_Long_DST_Time(const tDevice* M_NONNULL device,
                                           uint8_t* M_NONNULL       hours,
                                           uint8_t* M_NONNULL       minutes)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (is_Self_Test_Supported(device))
    {
        uint16_t longDSTTime = UINT16_C(0);
        uint8_t* smartData   = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(ATA_SMART_READ_DATA_SIZE, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (smartData == M_NULLPTR)
        {
            perror("calloc failure\n");
            return MEMORY_FAILURE;
        }
        if (SUCCESS == ata_SMART_Read_Data(device, smartData, ATA_SMART_READ_DATA_SIZE))
        {
            longDSTTime = smartData[ATA_SMART_EXTENDED_POLLING_TIME_BYTE_OFFSET];
            if (longDSTTime == UINT8_MAX)
            {
                longDSTTime = M_BytesTo2ByteValue(smartData[ATA_SMART_EXTENDED_POLLING_TIME_WORD_OFFSET + 1],
                                                  smartData[ATA_SMART_EXTENDED_POLLING_TIME_WORD_OFFSET]);
            }
            // convert the time to hours and minutes
            *hours   = C_CAST(uint8_t, longDSTTime / 60);
            *minutes = C_CAST(uint8_t, longDSTTime % 60);
            ret      = SUCCESS;
        }
        safe_free_aligned(&smartData);
    }
    return ret;
}

M_NONNULL_PARAM_LIST(1, 2, 3)
M_PARAM_RO(1)
M_PARAM_WO(2)
M_PARAM_WO(3)
static eReturnValues get_NVMe_Long_DST_Time(const tDevice* M_NONNULL device,
                                            uint8_t* M_NONNULL       hours,
                                            uint8_t* M_NONNULL       minutes)
{
    uint16_t longTestTime = le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.edstt);
    *hours                = C_CAST(uint8_t, longTestTime / UINT16_C(60));
    *minutes              = C_CAST(uint8_t, longTestTime % UINT16_C(60));
    return SUCCESS;
}

M_NONNULL_PARAM_LIST(1, 2, 3)
M_PARAM_RO(1)
M_PARAM_WO(2)
M_PARAM_WO(3)
static eReturnValues get_SCSI_Long_DST_Time(const tDevice* M_NONNULL device,
                                            uint8_t* M_NONNULL       hours,
                                            uint8_t* M_NONNULL       minutes)
{
    eReturnValues ret                            = NOT_SUPPORTED;
    uint16_t      longDSTTime                    = UINT16_C(0);
    bool          getTimeFromExtendedInquiryData = false;
    uint8_t*      controlMP =
        M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(MP_CONTROL_LEN + MODE_PARAMETER_HEADER_10_LEN, sizeof(uint8_t),
                                                         device->os_info.minimumAlignment));
    if (controlMP == M_NULLPTR)
    {
        perror("calloc failure!");
        return MEMORY_FAILURE;
    }
    // read the control MP to get the long DST time, but it is reported in SECONDS here
    if (SUCCESS == scsi_Mode_Sense_10(device, MP_CONTROL, MP_CONTROL_LEN + MODE_PARAMETER_HEADER_10_LEN, 0, true, false,
                                      MPC_DEFAULT_VALUES, controlMP))
    {
        longDSTTime = M_BytesTo2ByteValue(controlMP[MODE_PARAMETER_HEADER_10_LEN + 10],
                                          controlMP[MODE_PARAMETER_HEADER_10_LEN + 11]);
        if (longDSTTime == UINT16_MAX)
        {
            getTimeFromExtendedInquiryData = true;
        }
        else
        {
            // convert from the time in SECONDS to hours and minutes
            *hours   = C_CAST(uint8_t, longDSTTime / 3600);
            *minutes = C_CAST(uint8_t, (longDSTTime % 3600) / 60);
            ret      = SUCCESS;
        }
    }
    else
    {
        if (SUCCESS == scsi_Mode_Sense_6(device, MP_CONTROL, MP_CONTROL_LEN + MODE_PARAMETER_HEADER_6_LEN, 0, true,
                                         MPC_DEFAULT_VALUES, controlMP))
        {
            longDSTTime = M_BytesTo2ByteValue(controlMP[MODE_PARAMETER_HEADER_6_LEN + 10],
                                              controlMP[MODE_PARAMETER_HEADER_6_LEN + 11]);
            if (longDSTTime == UINT16_MAX)
            {
                getTimeFromExtendedInquiryData = true;
            }
            else
            {
                // convert from the time in SECONDS to hours and minutes
                *hours   = C_CAST(uint8_t, longDSTTime / 3600);
                *minutes = C_CAST(uint8_t, (longDSTTime % 3600) / 60);
                ret      = SUCCESS;
            }
        }
        else
        {
            ret                            = NOT_SUPPORTED;
            getTimeFromExtendedInquiryData = true; // some crappy USB bridges may not support the mode page, but will
                                                   // support the VPD page, so attempt to read the VPD page anyways
        }
    }
    safe_free_aligned(&controlMP);
    if (getTimeFromExtendedInquiryData)
    {
        uint8_t* extendedInqyData = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(VPD_EXTENDED_INQUIRY_LEN, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (extendedInqyData == M_NULLPTR)
        {
            perror("calloc failure!\n");
            return MEMORY_FAILURE;
        }
        if (SUCCESS ==
            scsi_Inquiry(device, extendedInqyData, VPD_EXTENDED_INQUIRY_LEN, EXTENDED_INQUIRY_DATA, true, false))
        {
            // time is reported in MINUTES here
            longDSTTime = M_BytesTo2ByteValue(extendedInqyData[10], extendedInqyData[11]);
            // convert the time to hours and minutes
            *hours   = C_CAST(uint8_t, longDSTTime / 60);
            *minutes = C_CAST(uint8_t, longDSTTime % 60);
            ret      = SUCCESS;
        }
        safe_free_aligned(&extendedInqyData);
    }
    return ret;
}

eReturnValues get_Long_DST_Time(const tDevice* device, uint8_t* hours, uint8_t* minutes)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (hours == M_NULLPTR || minutes == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    switch (device->drive_info.drive_type)
    {
    case ATA_DRIVE:
        ret = get_ATA_Long_DST_Time(device, hours, minutes);
        break;
    case NVME_DRIVE:
        ret = get_NVMe_Long_DST_Time(device, hours, minutes);
        break;
    case SCSI_DRIVE:
        ret = get_SCSI_Long_DST_Time(device, hours, minutes);
        break;
    default:
        break;
    }
    return ret;
}

bool get_Error_LBA_From_DST_Log(const tDevice* device, uint64_t* lba)
{
    bool isValidLBA = false;
    *lba            = UINT64_MAX; // set to something crazy in case caller ignores return type
    dstLogEntries dstEntries;
    safe_memset(&dstEntries, sizeof(dstLogEntries), 0, sizeof(dstLogEntries));
    if (get_DST_Log_Entries(device, &dstEntries) == SUCCESS)
    {
        if (dstEntries.numberOfEntries > 0 && dstEntries.dstEntry[0].descriptorValid &&
            M_Nibble1(dstEntries.dstEntry[0].selfTestExecutionStatus) == 0x07)
        {
            isValidLBA = true;
            *lba       = dstEntries.dstEntry[0].lbaOfFailure;
        }
    }
    return isValidLBA;
}

static eReturnValues repair_LBA_And_Log_Result(const tDevice* device,
                                               bool           passthroughWrite,
                                               bool           autoWriteReassign,
                                               bool           autoReadReassign,
                                               errorLBA*      errorList,
                                               uint64_t*      errorIndex,
                                               uint64_t*      totalErrors)
{
    eReturnValues ret = SUCCESS;
    if (device->deviceVerbosity > VERBOSITY_QUIET)
    {
        printf("Repairing LBA %" PRIu64 "\n", errorList[*errorIndex].errorAddress);
    }
    // add the LBA to the error list we have going, then repair it
    errorList[*errorIndex].repairStatus = NOT_REPAIRED;
    ret = repair_LBA(device, &errorList[*errorIndex], passthroughWrite, autoWriteReassign, autoReadReassign);
    ++(*totalErrors);
    ++(*errorIndex);
    return ret;
}

#define DEFAULT_READ_AROUND_RANGE UINT64_C(10000) // 10,000 LBAs is the default read around range

static M_INLINE uint64_t half_read_around_range(uint64_t range)
{
    return range / UINT64_C(2);
}

static eReturnValues read_Around_Defect(const tDevice* device,
                                        bool           passthroughWrite,
                                        uint64_t       readAroundRange,
                                        errorLBA*      errorList,
                                        uint64_t*      errorIndex,
                                        uint64_t*      totalErrors,
                                        bool           autoReadReassign,
                                        bool           autoWriteReassign,
                                        uint16_t       errorLimit)
{
    eReturnValues ret             = SUCCESS;
    uint64_t      readAroundStart = UINT64_C(0);
    // 10000 LBAs total (as long as we don't go over the end of the drive)
    if (errorList[*errorIndex].errorAddress > half_read_around_range(readAroundRange))
    {
        readAroundStart = errorList[*errorIndex].errorAddress - half_read_around_range(readAroundRange);
    }
    if (passthroughWrite)
    {
        if (device->drive_info.bridge_info.childDeviceMaxLba - errorList[*errorIndex].errorAddress <
            half_read_around_range(readAroundRange))
        {
            readAroundRange = device->drive_info.bridge_info.childDeviceMaxLba - readAroundStart;
        }
    }
    else
    {
        if (device->drive_info.deviceMaxLba - errorList[*errorIndex].errorAddress <
            half_read_around_range(readAroundRange))
        {
            readAroundRange = device->drive_info.deviceMaxLba - readAroundStart;
        }
    }
    // not using generic_tests.h since we don't have a way to force ATA vs SCSI passthrough command for
    // this, and we have times where we must do a passthrough write (USB emulation nonsense) first try
    // verifying the whole thing at once so we can skip the loop below if it is good
    eReturnValues verify = SUCCESS;
    if (passthroughWrite)
    {
        verify = ata_Read_Verify(device, readAroundStart, C_CAST(uint32_t, readAroundRange));
    }
    else
    {
        verify = verify_LBA(device, readAroundStart, C_CAST(uint32_t, readAroundRange));
    }
    if (SUCCESS != verify)
    {
        // there is another bad sector we need to find and fix...
        uint8_t logicalPerPhysical =
            C_CAST(uint8_t, device->drive_info.devicePhyBlockSize / device->drive_info.deviceBlockSize);
        if (passthroughWrite)
        {
            logicalPerPhysical = C_CAST(uint8_t, device->drive_info.bridge_info.childDevicePhyBlockSize /
                                                     device->drive_info.bridge_info.childDeviceBlockSize);
        }
        for (uint64_t iter = readAroundStart; iter < (readAroundStart + readAroundRange); iter += logicalPerPhysical)
        {
            if (*totalErrors > errorLimit)
            {
                break;
            }
            if (passthroughWrite)
            {
                verify = ata_Read_Verify(device, iter, logicalPerPhysical);
            }
            else
            {
                verify = verify_LBA(device, iter, logicalPerPhysical);
            }
            if (verify != SUCCESS)
            {
                errorList[*errorIndex].repairStatus = NOT_REPAIRED;
                errorList[*errorIndex].errorAddress = iter;

                ret = repair_LBA_And_Log_Result(device, passthroughWrite, autoWriteReassign, autoReadReassign,
                                                errorList, errorIndex, totalErrors);
                if (ret != SUCCESS)
                {
                    break;
                }
            }
        }
    }
    return ret;
}

eReturnValues run_DST_And_Clean(const tDevice*          device,
                                uint16_t                errorLimit,
                                custom_Update           updateFunction,
                                void*                   updateData,
                                ptrDSTAndCleanErrorList externalErrorList,
                                bool*                   repaired)
{
    eReturnValues ret              = SUCCESS; // assume this works successfully
    errorLBA*     errorList        = M_NULLPTR;
    uint64_t*     errorIndex       = M_NULLPTR;
    uint64_t      localErrorIndex  = UINT64_C(0);
    uint64_t      totalErrors      = UINT64_C(0);
    bool          unableToRepair   = false;
    bool          passthroughWrite = false;
    M_USE_UNUSED(updateFunction);
    M_USE_UNUSED(updateData);
    if (is_Sector_Size_Emulation_Active(device))
    {
        passthroughWrite = true; // in this case, since sector size emulation is active, we need to issue a passthrough
                                 // command for the repair instead of a standard interface command. - TJE
    }
    if (externalErrorList == M_NULLPTR)
    {
        size_t errorListAllocation = SIZE_T_C(0);
        if (errorLimit < 1)
        {
            errorListAllocation = sizeof(errorLBA); // allocate a list length of 1..that way we have something if the
                                                    // rest of the function does try and access this memory.
        }
        else
        {
            errorListAllocation = errorLimit * sizeof(errorLBA);
        }
        errorList = C_CAST(
            errorLBA*, safe_calloc_aligned(errorListAllocation, sizeof(errorLBA), device->os_info.minimumAlignment));
        if (errorList == M_NULLPTR)
        {
            perror("calloc failure\n");
            return MEMORY_FAILURE;
        }
        errorIndex = &localErrorIndex;
    }
    else
    {
        errorList  = externalErrorList->ptrToErrorList;
        errorIndex = externalErrorList->errorIndex;
    }

    bool autoReadReassign  = false;
    bool autoWriteReassign = false;
    if (SUCCESS != get_Automatic_Reallocation_Support(device, &autoWriteReassign, &autoReadReassign))
    {
        autoWriteReassign = true; // just in case this fails, default to previous behavior
    }
    // this is essentially a loop over the sequential read function
    while (totalErrors <= errorLimit)
    {
        dstTiming timing = get_DST_Polling_Interval(device, DST_TYPE_SHORT, false);
        // start DST
        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
        {
            print_str("Running DST.\n");
        }

        if (SUCCESS == run_DST(device, DST_TYPE_SHORT, false, false, true))
        {
            // poll until it finished
            ret = poll_DST_Progress(device, timing, true, false);
            if (ret == ABORTED || ret == SUCCESS)
            {
                break;
            }
            else
            {
                if (get_Error_LBA_From_DST_Log(device, &errorList[*errorIndex].errorAddress))
                {
                    totalErrors++; // Increment the number of errors we have seen
                    if (totalErrors > errorLimit)
                    {
                        break;
                    }

                    ret = repair_LBA_And_Log_Result(device, passthroughWrite, autoWriteReassign, autoReadReassign,
                                                    errorList, errorIndex, &totalErrors);

                    if (repaired != M_NULLPTR)
                    {
                        *repaired = true;
                    }
                    (*errorIndex)++;
                    if (ret == OS_PASSTHROUGH_FAILURE)
                    {
                        unableToRepair = true;
                    }
                    if (ret != SUCCESS)
                    {
                        break;
                    }
                    // Now we need to read around the LBA we repaired to make sure there aren't others around
                    ret = read_Around_Defect(device, passthroughWrite, DEFAULT_READ_AROUND_RANGE, errorList, errorIndex,
                                             &totalErrors, autoReadReassign, autoWriteReassign, errorLimit);
                }
                else
                {
                    unableToRepair = true;
                    ret            = FAILURE;
                    break;
                }
            }
        }
        else
        {
            // couldn't start a DST...so just break out of the loop
            break;
        }
    }
    // printf("totalErrors:  %" PRIu64 "\n", totalErrors);
    // printf("errorIndex:  %" PRIu64 "\n", errorIndex);
    // printf("errorLimit:  %" PRIu16 "\n", errorLimit);
    if (totalErrors > errorLimit)
    {
        ret = FAILURE;
    }
    if (externalErrorList == M_NULLPTR)
    {
        if (device->deviceVerbosity > VERBOSITY_QUIET)
        {
            if (totalErrors > 0)
            {
                print_LBA_Error_List(errorList, C_CAST(uint16_t, *errorIndex));
                if (unableToRepair)
                {
                    print_str("Other errors were found during DST, but were unable to be repaired.\n");
                }
            }
            else if (unableToRepair)
            {
                print_str("An error was detected during DST but it is unable to be repaired.\n");
            }
            else
            {
                print_str("No bad LBAs detected during DST and Clean.\n");
            }
        }
        safe_free_aligned_core(C_CAST(void**, &errorList));
    }
    return ret;
}

#define ENABLE_DST_LOG_DEBUG 0 // set to non zero to enable this debug.

static eReturnValues get_ATA_Extended_DST_Log_Entries(const tDevice* device, ptrDstLogEntries entries)
{
    eReturnValues ret     = NOT_SUPPORTED;
    uint32_t      logSize = UINT32_C(0);
    if (SUCCESS == get_ATA_Log_Size(device, ATA_LOG_EXTENDED_SMART_SELF_TEST_LOG, &logSize, true, false) && logSize > 0)
    {
        uint8_t* selfTestResults = M_NULLPTR;
        uint32_t extLogSize      = logSize;
        selfTestResults          = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(extLogSize, sizeof(uint8_t), device->os_info.minimumAlignment));
        uint16_t lastPage = C_CAST(uint16_t, (extLogSize / LEGACY_DRIVE_SEC_SIZE) - 1); // zero indexed
        if (selfTestResults == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        // read the extended self test results log with read log ext
        if (SUCCESS == get_ATA_Log(device, ATA_LOG_EXTENDED_SMART_SELF_TEST_LOG, M_NULLPTR, M_NULLPTR, true, false,
                                   true, selfTestResults, extLogSize, M_NULLPTR, 0, 0))
        {
            ret                    = SUCCESS;
            entries->logType       = DST_LOG_TYPE_ATA;
            uint16_t selfTestIndex = M_BytesTo2ByteValue(selfTestResults[3], selfTestResults[2]);
            if (selfTestIndex > 0) // we know DST has been run at least once...
            {
                uint8_t descriptorLength = UINT8_C(26);
                // To calculate page number:
                //    There are 19 descriptors in 512 bytes.
                //    There are 4 reserved bytes in each sector + 18 at the end
                //    26 * 19 + 18 = 512;

                uint16_t zeroBasedIndex  = selfTestIndex - UINT16_C(1);
                uint16_t pageNumber      = (zeroBasedIndex) / UINT16_C(19);
                uint16_t entryWithinPage = (zeroBasedIndex) % UINT16_C(19);
                uint16_t descriptorOffset =
                    C_CAST(uint16_t, (entryWithinPage * descriptorLength) + UINT16_C(4)); // offset withing the page
#if ENABLE_DST_LOG_DEBUG
                printf("starting at page number = %u\n", pageNumber);
                printf("lastPage = %u\n", lastPage);
#endif
                uint32_t offset      = (pageNumber > UINT32_C(0)) ? descriptorOffset * pageNumber : descriptorOffset;
                uint32_t firstOffset = offset;
                uint16_t counter     = UINT16_C(0); // when this get's larger than our max, we need to break out of the
                                                    // loop. This is always incremented. - TJE
                uint16_t maxEntries = C_CAST(
                    uint16_t,
                    (lastPage + UINT16_C(1)) *
                        UINT16_C(19)); // this should get us some multiple of 19 based on the number of pages we read.
#if ENABLE_DST_LOG_DEBUG
                printf("maxEntries = %u\n", maxEntries);
#endif
                while (counter <= maxEntries)
                {
#if ENABLE_DST_LOG_DEBUG
                    printf("offset = %u\n", offset);
#endif
                    if (counter > 0 && offset == firstOffset)
                    {
                        // we're back at the beginning and need to exit the loop.
                        break;
                    }
                    if (!is_Empty(&selfTestResults[offset], descriptorLength)) // invalid entires will be all zeros-TJE
                    {
                        entries->dstEntry[entries->numberOfEntries].descriptorValid = true;
                        entries->dstEntry[entries->numberOfEntries].selfTestRun     = selfTestResults[offset];
                        entries->dstEntry[entries->numberOfEntries].selfTestExecutionStatus =
                            selfTestResults[offset + 1];
                        entries->dstEntry[entries->numberOfEntries].lifetimeTimestamp =
                            M_BytesTo2ByteValue(selfTestResults[offset + 3], selfTestResults[offset + 2]);
                        entries->dstEntry[entries->numberOfEntries].checkPointByte = selfTestResults[offset + 4];
                        entries->dstEntry[entries->numberOfEntries].lbaOfFailure =
                            M_BytesTo8ByteValue(0, 0, selfTestResults[offset + 10], selfTestResults[offset + 9],
                                                selfTestResults[offset + 8], selfTestResults[offset + 7],
                                                selfTestResults[offset + 6], selfTestResults[offset + 5]);
                        // if LBA field is all F's, this is meant to signify an invalid value like T10 specs say to do -
                        // TJE
                        if (entries->dstEntry[entries->numberOfEntries].lbaOfFailure == MAX_48_BIT_LBA)
                        {
                            entries->dstEntry[entries->numberOfEntries].lbaOfFailure = UINT64_MAX;
                        }

                        safe_memcpy(&entries->dstEntry[entries->numberOfEntries].ataVendorSpecificData[0], 15,
                                    &selfTestResults[offset + 11], 15);
                        // dummy up sense data...
                        switch (M_Nibble1(entries->dstEntry[entries->numberOfEntries].selfTestExecutionStatus))
                        {
                        case 0:
                        case 15:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey = SENSE_KEY_NO_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode          = 0;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier = 0;
                            // NOTE: This is a workaround to clear out the LBA on success:
                            entries->dstEntry[entries->numberOfEntries].lbaOfFailure = UINT64_MAX;
                            break;
                        case 1:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_ABORTED_COMMAND;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x81;
                            break;
                        case 2:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_ABORTED_COMMAND;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x82;
                            break;
                        case 3:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_ABORTED_COMMAND;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x83;
                            break;
                        case 4:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x84;
                            break;
                        case 5:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x85;
                            break;
                        case 6:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x86;
                            break;
                        case 7:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey = SENSE_KEY_MEDIUM_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x87;
                            break;
                        case 8:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x88;
                            break;
                        default: // unspecified
                            break;
                        }
                        ++entries->numberOfEntries;
                    }
                    if (offset > descriptorLength)
                    {
                        uint32_t offsetCheck = UINT32_C(0);
                        // offset needs to subtract since we work backwards from the end (until rollover)
                        offset -= descriptorLength;
                        offsetCheck = offset;
#if ENABLE_DST_LOG_DEBUG
                        printf("\toffsetCheck = %u ", offsetCheck);
#endif
                        if (pageNumber > 0 &&
                            offsetCheck >
                                C_CAST(uint32_t, (LEGACY_DRIVE_SEC_SIZE *
                                                  pageNumber))) // this cast is extremely stupid. Apparently MSFT's
                                                                // UINT16_C doesn't actually force it to unsigned
                                                                // properly, so this generates an unneccessary warning
                        {
                            offsetCheck -= (LEGACY_DRIVE_SEC_SIZE * pageNumber);
#if ENABLE_DST_LOG_DEBUG
                            printf("\tsubtracting %u", (LEGACY_DRIVE_SEC_SIZE * pageNumber));
#endif
                        }
#if ENABLE_DST_LOG_DEBUG
                        print_str("\n");
#endif
                        if (offsetCheck < 4 || offsetCheck > UINT32_C(472))
                        {
                            if (pageNumber > 0)
                            {
                                --pageNumber;
                            }
                            else
                            {
                                pageNumber = lastPage;
                            }
                            offset = UINT32_C(472) + (pageNumber * LEGACY_DRIVE_SEC_SIZE);
                        }
                    }
                    else
                    {
#if ENABLE_DST_LOG_DEBUG
                        print_str("\tsetting offset to 472 on last page read\n");
#endif
                        offset = UINT32_C(472) + (lastPage * LEGACY_DRIVE_SEC_SIZE);
                    }
                    ++counter;
                    if (entries->numberOfEntries >= MAX_DST_ENTRIES)
                    {
                        break;
                    }
                }
            }
        }
        safe_free_aligned(&selfTestResults);
    }
    return ret;
}

static eReturnValues get_ATA_SMART_DST_Log_Entries(const tDevice* device, ptrDstLogEntries entries)
{
    eReturnValues ret     = NOT_SUPPORTED;
    uint32_t      logSize = UINT32_C(0);
    if (SUCCESS == get_ATA_Log_Size(device, ATA_LOG_SMART_SELF_TEST_LOG, &logSize, false, true) && logSize > 0)
    {
        uint8_t* selfTestResults = M_NULLPTR;
        selfTestResults          = C_CAST(
            uint8_t*, safe_calloc_aligned(LEGACY_DRIVE_SEC_SIZE, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (selfTestResults == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        // read the self tests results log with SMART read log
        if (SUCCESS == ata_SMART_Read_Log(device, ATA_LOG_SMART_SELF_TEST_LOG, selfTestResults, LEGACY_DRIVE_SEC_SIZE))
        {
            ret                   = SUCCESS;
            entries->logType      = DST_LOG_TYPE_ATA;
            uint8_t selfTestIndex = selfTestResults[508];
            if (selfTestIndex > 0)
            {
                uint8_t descriptorLength = UINT8_C(24);
                uint8_t descriptorOffset =
                    C_CAST(uint8_t, ((selfTestIndex * descriptorLength) - descriptorLength) + C_CAST(uint8_t, 2));
                uint16_t offset  = descriptorOffset;
                uint16_t counter = UINT16_C(0); // when this get's larger than our max, we need to break out of the
                                                // loop. This is always incremented. - TJE
                while (counter < MAX_DST_ENTRIES /*&& counter < 21*/) // max of 21 dst entries in this log
                {
                    if (!is_Empty(&selfTestResults[offset], descriptorLength))
                    {
                        entries->dstEntry[entries->numberOfEntries].descriptorValid = true;
                        entries->dstEntry[entries->numberOfEntries].selfTestRun     = selfTestResults[offset];
                        entries->dstEntry[entries->numberOfEntries].selfTestExecutionStatus =
                            selfTestResults[offset + 1];
                        entries->dstEntry[entries->numberOfEntries].lifetimeTimestamp =
                            M_BytesTo2ByteValue(selfTestResults[offset + 3], selfTestResults[offset + 2]);
                        entries->dstEntry[entries->numberOfEntries].checkPointByte = selfTestResults[offset + 4];
                        entries->dstEntry[entries->numberOfEntries].lbaOfFailure =
                            M_BytesTo4ByteValue(selfTestResults[offset + 8], selfTestResults[offset + 7],
                                                selfTestResults[offset + 6], selfTestResults[offset + 5]);
                        safe_memcpy(&entries->dstEntry[entries->numberOfEntries].ataVendorSpecificData[0], 15,
                                    &selfTestResults[offset + 9], 15);
                        // if LBA field is all F's, this is meant to signify an invalid value like T10 specs say to do -
                        // TJE filtering 28bit all F's and 32bit all F's since it is not clear exactly how many drives
                        // will report this invalid value -TJE
                        if (entries->dstEntry[entries->numberOfEntries].lbaOfFailure == MAX_28_BIT_LBA ||
                            entries->dstEntry[entries->numberOfEntries].lbaOfFailure == UINT32_MAX)
                        {
                            entries->dstEntry[entries->numberOfEntries].lbaOfFailure = UINT64_MAX;
                        }

                        // dummy up sense data...
                        switch (M_Nibble1(entries->dstEntry[entries->numberOfEntries].selfTestExecutionStatus))
                        {
                        case 0:
                        case 15:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey = SENSE_KEY_NO_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode          = 0;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier = 0;
                            // NOTE: This is a workaround to clear out the LBA on success:
                            entries->dstEntry[entries->numberOfEntries].lbaOfFailure = UINT64_MAX;
                            break;
                        case 1:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_ABORTED_COMMAND;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x81;
                            break;
                        case 2:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_ABORTED_COMMAND;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x82;
                            break;
                        case 3:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_ABORTED_COMMAND;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x83;
                            break;
                        case 4:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x84;
                            break;
                        case 5:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x85;
                            break;
                        case 6:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x86;
                            break;
                        case 7:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey = SENSE_KEY_MEDIUM_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x87;
                            break;
                        case 8:
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey =
                                SENSE_KEY_HARDWARE_ERROR;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = 0x40;
                            entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                                0x88;
                            break;
                        default: // unspecified
                            break;
                        }
                        ++entries->numberOfEntries;
                    }
                    if (offset > descriptorLength)
                    {
                        uint16_t offsetCheck = UINT16_C(0);
                        // offset needs to subtract since we work backwards from the end (until rollover)
                        offset -= descriptorLength;
                        offsetCheck = offset;
                        if (offsetCheck > LEGACY_DRIVE_SEC_SIZE)
                        {
                            offsetCheck -= LEGACY_DRIVE_SEC_SIZE;
                        }
                        if (offsetCheck < 2 || offsetCheck > 482)
                        {
                            // need to adjust offset to 482 (since we are rolling over back to beginning)
                            offset = 482;
                        }
                    }
                    else
                    {
                        // go to last descriptor
                        offset = 482;
                    }
                    ++counter;
                }
            }
        }
        safe_free_aligned(&selfTestResults);
    }
    return ret;
}

static eReturnValues get_ATA_DST_Log_Entries(const tDevice* device, ptrDstLogEntries entries)
{
    eReturnValues ret = NOT_SUPPORTED;
    // used for compatibility purposes with drives that may have GPL, but not support the ext log...
    // device->drive_info.ata_Options.generalPurposeLoggingSupported = false;//for debugging SMART log version
    if (device->drive_info.ata_Options.generalPurposeLoggingSupported)
    {
        ret = get_ATA_Extended_DST_Log_Entries(device, entries);
        if (ret == NOT_SUPPORTED && (is_SMART_Enabled(device) && is_SMART_Error_Logging_Supported(device)))
        {
            // try the SMART log instead
            ret = get_ATA_SMART_DST_Log_Entries(device, entries);
        }
    }
    else if (is_SMART_Enabled(device) && is_SMART_Error_Logging_Supported(device))
    {
        ret = get_ATA_SMART_DST_Log_Entries(device, entries);
    }
    return ret;
}

M_PARAM_RO(1)
M_PARAM_WO(2)
static eReturnValues get_SCSI_DST_Log_Entries(const tDevice* M_NONNULL device, ptrDstLogEntries M_NONNULL entries)
{
    eReturnValues ret = NOT_SUPPORTED;
    DECLARE_ZERO_INIT_ARRAY(uint8_t, dstLog, LP_SELF_TEST_RESULTS_LEN);

    if (entries == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    if (SUCCESS == scsi_Log_Sense_Cmd(device, false, LPC_CUMULATIVE_VALUES, LP_SELF_TEST_RESULTS, 0, 1, dstLog,
                                      LP_SELF_TEST_RESULTS_LEN))
    {
        uint16_t pageLength      = M_BytesTo2ByteValue(dstLog[2], dstLog[3]);
        ret                      = SUCCESS;
        entries->logType         = DST_LOG_TYPE_SCSI;
        entries->numberOfEntries = 0;
        for (uint32_t offset = UINT32_C(4);
             offset < C_CAST(uint32_t, pageLength + UINT16_C(4)) && offset < LP_SELF_TEST_RESULTS_LEN &&
             entries->numberOfEntries < MAX_DST_ENTRIES;
             offset += 20)
        {
            if (!is_Empty(&dstLog[offset + 4], 16))
            {
                entries->dstEntry[entries->numberOfEntries].descriptorValid = true;
                entries->dstEntry[entries->numberOfEntries].selfTestExecutionStatus =
                    C_CAST(uint8_t, M_Nibble0(dstLog[offset + 4]) << 4);
                entries->dstEntry[entries->numberOfEntries].selfTestRun    = M_Nibble1(dstLog[offset + 4]) >> 1;
                entries->dstEntry[entries->numberOfEntries].checkPointByte = dstLog[offset + 5];
                entries->dstEntry[entries->numberOfEntries].lifetimeTimestamp =
                    M_BytesTo2ByteValue(dstLog[offset + 6], dstLog[offset + 7]);
                entries->dstEntry[entries->numberOfEntries].lbaOfFailure = M_BytesTo8ByteValue(
                    dstLog[offset + 8], dstLog[offset + 9], dstLog[offset + 10], dstLog[offset + 11],
                    dstLog[offset + 12], dstLog[offset + 13], dstLog[offset + 14], dstLog[offset + 15]);
                entries->dstEntry[entries->numberOfEntries].scsiSenseCode.senseKey = M_Nibble0(dstLog[offset + 16]);
                entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCode = dstLog[offset + 17];
                entries->dstEntry[entries->numberOfEntries].scsiSenseCode.additionalSenseCodeQualifier =
                    dstLog[offset + 18];
                entries->dstEntry[entries->numberOfEntries].scsiVendorSpecificByte = dstLog[offset + 19];
                ++entries->numberOfEntries;
            }
        }
    }
    return ret;
}

M_PARAM_RO(1)
M_PARAM_WO(2)
static eReturnValues get_NVMe_DST_Log_Entries(const tDevice* M_NONNULL device, ptrDstLogEntries M_NONNULL entries)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (entries == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    if (is_Self_Test_Supported(device))
    {
        nvmeGetLogPageCmdOpts dstLogParms;
        safe_memset(&dstLogParms, sizeof(nvmeGetLogPageCmdOpts), 0, sizeof(nvmeGetLogPageCmdOpts));
        DECLARE_ZERO_INIT_ARRAY(uint8_t, nvmeDSTLog, NVME_LOG_SIZE_SELF_TEST);
        dstLogParms.addr    = nvmeDSTLog;
        dstLogParms.dataLen = NVME_LOG_SIZE_SELF_TEST;
        dstLogParms.lid     = NVME_LOG_DEV_SELF_TEST_ID;
        dstLogParms.nsid    = UINT32_MAX;
        if (SUCCESS == nvme_Get_Log_Page(device, &dstLogParms))
        {
            ret                      = SUCCESS;
            entries->logType         = DST_LOG_TYPE_NVME;
            entries->numberOfEntries = 0;
            for (uint32_t offset = UINT32_C(4); offset < NVME_LOG_SIZE_SELF_TEST && entries->numberOfEntries < 20;
                 offset += 28) // maximum of 20 NVMe DST log entires
            {
                // check if the entry is valid by checking for zeros
                if (!is_Empty(&nvmeDSTLog[offset], 28) &&
                    M_Nibble0(nvmeDSTLog[offset + 0]) != 0x0F) // 0F in NVMe is an unused entry.
                {
                    entries->dstEntry[entries->numberOfEntries].selfTestRun = M_Nibble1(nvmeDSTLog[offset + 0]);
                    entries->dstEntry[entries->numberOfEntries].selfTestExecutionStatus =
                        M_Nibble0(nvmeDSTLog[offset + 0]);
                    entries->dstEntry[entries->numberOfEntries].segmentNumber = nvmeDSTLog[offset + 1];
                    entries->dstEntry[entries->numberOfEntries].powerOnHours =
                        M_BytesTo8ByteValue(nvmeDSTLog[offset + 11], nvmeDSTLog[offset + 10], nvmeDSTLog[offset + 9],
                                            nvmeDSTLog[offset + 8], nvmeDSTLog[offset + 7], nvmeDSTLog[offset + 6],
                                            nvmeDSTLog[offset + 5], nvmeDSTLog[offset + 4]);
                    if (nvmeDSTLog[offset + 2] & BIT0)
                    {
                        entries->dstEntry[entries->numberOfEntries].nsidValid = true;
                        entries->dstEntry[entries->numberOfEntries].namespaceID =
                            M_BytesTo4ByteValue(nvmeDSTLog[offset + 15], nvmeDSTLog[offset + 14],
                                                nvmeDSTLog[offset + 13], nvmeDSTLog[offset + 12]);
                    }
                    if (nvmeDSTLog[offset + 2] & BIT1) // check if flba is set
                    {
                        entries->dstEntry[entries->numberOfEntries].lbaOfFailure = M_BytesTo8ByteValue(
                            nvmeDSTLog[offset + 23], nvmeDSTLog[offset + 22], nvmeDSTLog[offset + 21],
                            nvmeDSTLog[offset + 20], nvmeDSTLog[offset + 19], nvmeDSTLog[offset + 18],
                            nvmeDSTLog[offset + 17], nvmeDSTLog[offset + 16]);
                    }
                    else
                    {
                        // this is an invalid LBA value, so it can be filtered out with existing SCSI/ATA code
                        entries->dstEntry[entries->numberOfEntries].lbaOfFailure = UINT64_MAX;
                    }
                    if (nvmeDSTLog[offset + 2] & BIT2)
                    {
                        entries->dstEntry[entries->numberOfEntries].nvmeStatus.statusCodeTypeValid = true;
                        entries->dstEntry[entries->numberOfEntries].nvmeStatus.statusCodeType = nvmeDSTLog[offset + 24];
                    }
                    if (nvmeDSTLog[offset + 2] & BIT3)
                    {
                        entries->dstEntry[entries->numberOfEntries].nvmeStatus.statusCodeValid = true;
                        entries->dstEntry[entries->numberOfEntries].nvmeStatus.statusCode = nvmeDSTLog[offset + 25];
                    }
                    entries->dstEntry[entries->numberOfEntries].nvmeVendorSpecificWord =
                        M_BytesTo2ByteValue(nvmeDSTLog[offset + 27], nvmeDSTLog[offset + 26]);
                    // increment the number of entries since we found another good one!
                    entries->dstEntry[entries->numberOfEntries].descriptorValid = true;
                    ++(entries->numberOfEntries);
                }
            }
        }
        else
        {
            ret = FAILURE;
        }
    }
    return ret;
}

eReturnValues get_DST_Log_Entries(const tDevice* device, ptrDstLogEntries entries)
{
    switch (device->drive_info.drive_type)
    {
    case ATA_DRIVE:
        return get_ATA_DST_Log_Entries(device, entries);
    case NVME_DRIVE:
        return get_NVMe_DST_Log_Entries(device, entries);
    case SCSI_DRIVE:
        return get_SCSI_DST_Log_Entries(device, entries);
    default:

        if (entries != M_NULLPTR)
        {
            safe_memset(entries, sizeof(dstLogEntries), 0, sizeof(dstLogEntries));
        }

        return NOT_SUPPORTED;
    }
}

static void get_ATA_Self_Test_Code_String(uint8_t selfTestRun, char* M_NONNULL selfTestRunString, size_t maxLength)
{
    if (selfTestRunString != M_NULLPTR && maxLength > SIZE_T_C(0))
    {
        switch (selfTestRun)
        {
        case ATA_SMART_OFFLINE_DATA_COLLECTION:
            snprintf_err_handle(selfTestRunString, maxLength, "Offline Data Collect");
            break;
        case ATA_SMART_OFFLINE_SHORT_SELF_TEST: // short
            snprintf_err_handle(selfTestRunString, maxLength, "Short (offline)");
            break;
        case ATA_SMART_OFFLINE_EXTENDED_SELF_TEST: // extended
            snprintf_err_handle(selfTestRunString, maxLength, "Extended (offline)");
            break;
        case ATA_SMART_OFFLINE_CONVEYANCE_SELF_TEST: // conveyance
            snprintf_err_handle(selfTestRunString, maxLength, "Conveyance (offline)");
            break;
        case ATA_SMART_OFFLINE_SELECTIVE_SELF_TEST: // selective
            snprintf_err_handle(selfTestRunString, maxLength, "Selective (offline)");
            break;
        case ATA_SMART_OFFLINE_CAPTIVE_SHORT_SELF_TEST: // short
            snprintf_err_handle(selfTestRunString, maxLength, "Short (captive)");
            break;
        case ATA_SMART_OFFLINE_CAPTIVE_EXTENDED_SELF_TEST: // extended
            snprintf_err_handle(selfTestRunString, maxLength, "Extended (captive)");
            break;
        case ATA_SMART_OFFLINE_CAPTIVE_CONVEYANCE_SELF_TEST: // conveyance
            snprintf_err_handle(selfTestRunString, maxLength, "Conveyance (captive)");
            break;
        case ATA_SMART_OFFLINE_CAPTIVE_SELECTIVE_SELF_TEST: // selective
            snprintf_err_handle(selfTestRunString, maxLength, "Selective (captive)");
            break;
        default:
            if ((selfTestRun >= ATA_SMART_OFFLINE_VENDOR_SPECIFIC_40 &&
                 selfTestRun <= ATA_SMART_OFFLINE_VENDOR_SPECIFIC_7E) ||
                (selfTestRun >= ATA_SMART_OFFLINE_CAPTIVE_VENDOR_SPECIFIC_90 /*&& selfTestRun <= 0xFF*/))
            {
                snprintf_err_handle(selfTestRunString, maxLength, "Vendor Specific - %" PRIX8 "h", selfTestRun);
            }
            else
            {
                snprintf_err_handle(selfTestRunString, maxLength, "Unknown - %" PRIX8 "h", selfTestRun);
            }
            break;
        }
    }
}

static void get_SCSI_Self_Test_Code_String(uint8_t selfTestRun, char* selfTestRunString, size_t maxLength)
{
    if (selfTestRunString != M_NULLPTR && maxLength > SIZE_T_C(0))
    {
        switch (selfTestRun)
        {
        case SCSI_STC_UNUSED:
            snprintf_err_handle(selfTestRunString, maxLength, "Unknown (Not in spec)");
            break;
        case SCSI_STC_BACKGROUND_SHORT_SELF_TEST: // short
            snprintf_err_handle(selfTestRunString, maxLength, "Short (background)");
            break;
        case SCSI_STC_BACKGROUND_EXTENDED_SELF_TEST: // extended
            snprintf_err_handle(selfTestRunString, maxLength, "Extended (background)");
            break;
        case SCSI_STC_FOREGROUND_SHORT_SELF_TEST: // short
            snprintf_err_handle(selfTestRunString, maxLength, "Short (foreground)");
            break;
        case SCSI_STC_FOREGROUND_EXTENDED_SELF_TEST: // extended
            snprintf_err_handle(selfTestRunString, maxLength, "Extended (foreground)");
            break;
        default:
            snprintf_err_handle(selfTestRunString, maxLength, "Unknown - %" PRIX8 "h", selfTestRun);
            break;
        }
    }
}

static void get_NVMe_Self_Test_Code_String(uint8_t selfTestRun, char* selfTestRunString, size_t maxLength)
{
    if (selfTestRunString != M_NULLPTR && maxLength > SIZE_T_C(0))
    {
        switch (selfTestRun)
        {
        case NVME_STC_RESERVED:
            snprintf_err_handle(selfTestRunString, maxLength, "Reserved");
            break;
        case NVME_STC_SHORT: // short
            snprintf_err_handle(selfTestRunString, maxLength, "Short");
            break;
        case NVME_STC_EXTENDED: // extended
            snprintf_err_handle(selfTestRunString, maxLength, "Extended");
            break;
        case NVME_STC_VENDOR: // vendor specific
            snprintf_err_handle(selfTestRunString, maxLength, "Vendor Specific");
            break;
        default:
            snprintf_err_handle(selfTestRunString, maxLength, "Unknown - %" PRIX8 "h", selfTestRun);
            break;
        }
    }
}

static void get_Selftest_Execution_Status_String(dstLogType logtype,
                                                 uint8_t    selfTestExecutionStatus,
                                                 char*      status,
                                                 size_t     maxLength,
                                                 uint8_t    segmentNumber)
{
    if (status != M_NULLPTR && maxLength > SIZE_T_C(0))
    {
        if (logtype == DST_LOG_TYPE_NVME)
        {
            switch (M_Nibble1(selfTestExecutionStatus))
            {
            case 0:
                snprintf_err_handle(status, maxLength, "No Error");
                break;
            case 1:
                snprintf_err_handle(status, maxLength, "Aborted by command");
                break;
            case 2:
                snprintf_err_handle(status, maxLength, "Aborted by controller reset");
                break;
            case 3:
                snprintf_err_handle(status, maxLength, "Aborted by namespace removal");
                break;
            case 4:
                snprintf_err_handle(status, maxLength, "Aborted by NVM format");
                break;
            case 5:
                snprintf_err_handle(status, maxLength, "Unknown/Fatal Error");
                break;
            case 6:
                snprintf_err_handle(status, maxLength, "Unknown Segment Failure");
                break;
            case 7:
                snprintf_err_handle(status, maxLength, "Failed on segment %" PRIu8 "", segmentNumber);
                break;
            case 8:
                snprintf_err_handle(status, maxLength, "Aborted for Unknown Reason");
                break;
            default:
                snprintf_err_handle(status, maxLength, "Reserved");
                break;
            }
        }
        else
        {
            switch (M_Nibble1(selfTestExecutionStatus))
            {
            case 0:
                snprintf_err_handle(status, maxLength, "Success");
                break;
            case 1:
                snprintf_err_handle(status, maxLength, "Aborted by host");
                break;
            case 2:
                snprintf_err_handle(status, maxLength, "Interrupted by reset");
                break;
            case 3:
                snprintf_err_handle(status, maxLength, "Fatal Error - Unknown");
                break;
            case 4:
                snprintf_err_handle(status, maxLength, "Unknown Failure Type");
                break;
            case 5:
                snprintf_err_handle(status, maxLength, "Electrical Failure");
                break;
            case 6:
                snprintf_err_handle(status, maxLength, "Servo/Seek Failure");
                break;
            case 7:
                snprintf_err_handle(status, maxLength, "Read Failure");
                break;
            case 8:
                snprintf_err_handle(status, maxLength, "Handling Damage");
                break;
            case 0xF:
                snprintf_err_handle(status, maxLength, "In progress");
                break;
            default:
                snprintf_err_handle(status, maxLength, "Reserved");
                break;
            }
        }
    }
}

eReturnValues print_DST_Log_Entries(ptrDstLogEntries entries)
{

    if (entries == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    print_str("\n===DST Log===\n");
    if (entries->numberOfEntries == 0)
    {
        print_str("DST Has never been run/no DST entries found.\n");
    }
    else
    {
        // test types ATA
        // short (offline)
        // short (captive)
        // extended (offline)
        // extended (captive)
        // conveyance (offline)
        // conveyance (captive)
        // offline data collect
        // test types SCSI
        // short (background)
        // short (foreground)
        // extended (background)
        // extended (foreground)
        // test types NVMe
        // short
        // extended

        if (entries->logType == DST_LOG_TYPE_NVME)
        {
            // checkpoint = segment?
            printf(" # %-21s  %-9s  %-26s  %-14s  %-10s  %-9s\n", "Test", "Timestamp", "Execution Status", "Error LBA",
                   "Segment#", "Status Info");
        }
        else
        {
            printf(" # %-21s  %-9s  %-26s  %-14s  %-10s  %-9s\n", "Test", "Timestamp", "Execution Status", "Error LBA",
                   "Checkpoint", "Sense Info");
        }
        for (uint8_t iter = UINT8_C(0); iter < entries->numberOfEntries; ++iter)
        {
            if (!entries->dstEntry[iter].descriptorValid)
            {
                continue;
            }
            // #
            printf("%2" PRIu8 " ", iter + 1);
            // Test
#define SELF_TEST_RUN_STRING_MAX_LENGTH 22
            DECLARE_ZERO_INIT_ARRAY(char, selfTestRunString, SELF_TEST_RUN_STRING_MAX_LENGTH);
            if (entries->logType == DST_LOG_TYPE_ATA)
            {
                get_ATA_Self_Test_Code_String(entries->dstEntry[iter].selfTestRun, selfTestRunString,
                                              SELF_TEST_RUN_STRING_MAX_LENGTH);
            }
            else if (entries->logType == DST_LOG_TYPE_SCSI)
            {
                get_SCSI_Self_Test_Code_String(entries->dstEntry[iter].selfTestRun, selfTestRunString,
                                               SELF_TEST_RUN_STRING_MAX_LENGTH);
            }
            else if (entries->logType == DST_LOG_TYPE_NVME)
            {
                get_NVMe_Self_Test_Code_String(entries->dstEntry[iter].selfTestRun, selfTestRunString,
                                               SELF_TEST_RUN_STRING_MAX_LENGTH);
            }
            else // print the number
            {
                snprintf_err_handle(selfTestRunString, SELF_TEST_RUN_STRING_MAX_LENGTH, "Unknown - %" PRIX8 "h",
                                    entries->dstEntry[iter].selfTestRun);
            }
            printf("%-21s  ", selfTestRunString);
            // Timestamp
            printf("%-9" PRIu64 "  ", entries->dstEntry[iter].lifetimeTimestamp);
            // Execution Status
#define SELF_TEST_EXECUTION_STATUS_MAX_LENGTH 30
            DECLARE_ZERO_INIT_ARRAY(char, status, SELF_TEST_EXECUTION_STATUS_MAX_LENGTH);
            uint8_t percentRemaining = UINT8_C(0);
            if (entries->logType == DST_LOG_TYPE_ATA)
            {
                percentRemaining = M_Nibble0(entries->dstEntry[iter].selfTestExecutionStatus) * 10;
            }
            get_Selftest_Execution_Status_String(entries->logType, entries->dstEntry[iter].selfTestExecutionStatus,
                                                 status, SELF_TEST_EXECUTION_STATUS_MAX_LENGTH,
                                                 entries->dstEntry[iter].segmentNumber);
            if (percentRemaining > 0)
            {
                DECLARE_ZERO_INIT_ARRAY(char, percentRemainingString, 8);
                snprintf_err_handle(percentRemainingString, 8, " (%" PRIu8 "%%)", percentRemaining);
                safe_strcat(status, SELF_TEST_EXECUTION_STATUS_MAX_LENGTH, percentRemainingString);
            }
            printf("%-26s  ", status);
            // Error LBA
#define SELF_TEST_ERROR_LBA_STRING_MAX_LENGTH 21
            DECLARE_ZERO_INIT_ARRAY(char, errorLBAString, SELF_TEST_ERROR_LBA_STRING_MAX_LENGTH);
            if (entries->dstEntry[iter].lbaOfFailure == UINT64_MAX)
            {
                snprintf_err_handle(errorLBAString, SELF_TEST_ERROR_LBA_STRING_MAX_LENGTH, "None");
            }
            else
            {
                snprintf_err_handle(errorLBAString, SELF_TEST_ERROR_LBA_STRING_MAX_LENGTH, "%" PRIu64,
                                    entries->dstEntry[iter].lbaOfFailure);
            }
            printf("%-14s  ", errorLBAString);
            // Checkpoint/Segment number
            printf("%-10" PRIX8 "  ", entries->dstEntry[iter].checkPointByte);
            // Sense Info
#define SELF_TEST_SENSE_INFO_STRING_MAX_LENGTH 21
            DECLARE_ZERO_INIT_ARRAY(char, senseInfoString, SELF_TEST_SENSE_INFO_STRING_MAX_LENGTH);
            if (entries->logType == DST_LOG_TYPE_NVME)
            {
                // SCT - SC
#define NVM_STATUS_CODE_STR_LEN 10
                DECLARE_ZERO_INIT_ARRAY(char, sctVal, NVM_STATUS_CODE_STR_LEN);
                DECLARE_ZERO_INIT_ARRAY(char, scVal, NVM_STATUS_CODE_STR_LEN);
                if (entries->dstEntry[iter].nvmeStatus.statusCodeTypeValid)
                {
                    snprintf_err_handle(sctVal, NVM_STATUS_CODE_STR_LEN, "%02" PRIX8 "",
                                        entries->dstEntry[iter].nvmeStatus.statusCodeType);
                }
                else
                {
                    snprintf_err_handle(sctVal, NVM_STATUS_CODE_STR_LEN, "NA");
                }
                if (entries->dstEntry[iter].nvmeStatus.statusCodeValid)
                {
                    snprintf_err_handle(sctVal, NVM_STATUS_CODE_STR_LEN, "%02" PRIX8 "",
                                        entries->dstEntry[iter].nvmeStatus.statusCode);
                }
                else
                {
                    snprintf_err_handle(scVal, NVM_STATUS_CODE_STR_LEN, "NA");
                }
                snprintf_err_handle(senseInfoString, SELF_TEST_SENSE_INFO_STRING_MAX_LENGTH, "%s/%s", sctVal, scVal);
            }
            else
            {
                snprintf_err_handle(senseInfoString, SELF_TEST_SENSE_INFO_STRING_MAX_LENGTH,
                                    "%02" PRIX8 "/%02" PRIX8 "/%02" PRIX8,
                                    entries->dstEntry[iter].scsiSenseCode.senseKey,
                                    entries->dstEntry[iter].scsiSenseCode.additionalSenseCode,
                                    entries->dstEntry[iter].scsiSenseCode.additionalSenseCodeQualifier);
            }
            printf("%-9s\n", senseInfoString);
        }
        print_str("NOTE: DST Log entries are printed out in order from newest to oldest based ATA/SCSI/NVMe\n");
        print_str("      specifications on where to find the latest entry.\n\n");
    }
    return SUCCESS;
}
