// 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 format.c
// \brief This file defines the functions for performing some format unit 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 "format.h"
#include "logs.h"
#include "nvme_helper_func.h"
#include "platform_helper.h"
#include "seagate_operations.h"
#include "writesame.h"

bool is_Format_Unit_Supported(const tDevice* device, bool* fastFormatSupported)
{
    bool supported = false;
    if (fastFormatSupported != M_NULLPTR)
    {
        *fastFormatSupported = false; // make sure this defaults to false
    }
    scsiOperationCodeInfoRequest formatUnitSupReq;
    safe_memset(&formatUnitSupReq, sizeof(scsiOperationCodeInfoRequest), 0, sizeof(scsiOperationCodeInfoRequest));
    formatUnitSupReq.operationCode      = SCSI_FORMAT_UNIT_CMD;
    formatUnitSupReq.serviceActionValid = false;
    eSCSICmdSupport formatUnitSupport   = is_SCSI_Operation_Code_Supported(device, &formatUnitSupReq);
    // NOLINTBEGIN(bugprone-branch-clone)
    if (formatUnitSupport == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
    {
        supported = true;
        // if we made it here, then it's at least supported...not check the bit field for fast format support
        if (fastFormatSupported != M_NULLPTR) // make sure the pointer is valid
        {
            if (!(formatUnitSupReq.cdbUsageData[3] == 0xFF &&
                  formatUnitSupReq.cdbUsageData[4] == 0xFF)) // if both these bytes are FFh, then the drive conforms to
                                                             // SCSI2 where this was the "interleave" field
            {
                if (formatUnitSupReq.cdbUsageData[4] & 0x03) // checks that fast format bits are available for use.
                {
                    *fastFormatSupported = true;
                }
            }
        }
    }
    else if (formatUnitSupport != SCSI_CMD_SUPPORT_UNKNOWN)
    {
        supported = false;
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE && device->drive_info.interface_type == SCSI_INTERFACE)
    {
        // hack for old scsi drives that don't support the report supported op codes command. - TJE
        supported = true;
    }
    else
    {
        supported = false;
    }
    // NOLINTEND(bugprone-branch-clone)
    return supported;
}

eReturnValues get_Format_Progress(const tDevice* device, double* percentComplete)
{
    DECLARE_ZERO_INIT_ARRAY(uint8_t, senseData, SPC3_SENSE_LEN);
    *percentComplete = 0.0;
    if (SUCCESS == scsi_Request_Sense_Cmd(device, false, senseData, SPC3_SENSE_LEN))
    {
        uint8_t senseKey = UINT8_C(0);
        uint8_t asc      = UINT8_C(0);
        uint8_t ascq     = UINT8_C(0);
        uint8_t fru      = UINT8_C(0);
        get_Sense_Key_ASC_ASCQ_FRU(senseData, SPC3_SENSE_LEN, &senseKey, &asc, &ascq, &fru);
        if (senseKey == SENSE_KEY_NOT_READY)
        {
            if (asc == 0x04 && ascq == 0x04)
            {
                // get progress
                *percentComplete =
                    get_SCSI_Progress_Indicator_PercentD(M_BytesTo2ByteValue(senseData[16], senseData[17]));
                return IN_PROGRESS;
            }
            else
            {
                return UNKNOWN;
            }
        }
        else if (senseKey == SENSE_KEY_NO_ERROR)
        {
            return SUCCESS;
        }
        else
        {
            return FAILURE;
        }
    }
    else
    {
        return FAILURE;
    }
}

eReturnValues show_Format_Unit_Progress(const tDevice* device)
{
    eReturnValues ret             = UNKNOWN;
    double        percentComplete = 0.0;

    ret = get_Format_Progress(device, &percentComplete);

    if (ret == IN_PROGRESS)
    {
        printf("\tFormat Unit Progress = %3.2f%% \n", percentComplete);
        // add 0.005 to round up since this is what is happening in the %f print above (more or less) and
        // we really don't need a call to round() to accomplish this. This is also simple enough and close enough to
        // warn the user that the drive is not yet done with the format
        if (percentComplete + 0.005 >= 100.0)
        {
            print_str("\tWARNING: Even though progress reports 100%, the sense data indicates\n");
            print_str("\t         that a format is still in progress! Please wait an additional\n");
            print_str("\t         30 seconds and check again to see when the sense data no longer\n");
            print_str("\t         indicates that a format is in progress!\n");
        }
    }
    else if (ret == SUCCESS)
    {
        print_str("\tFormat Unit command is not currently in progress. It is either complete or has not been run.\n");
    }
    else
    {
        print_str("\tError occurred while retrieving format unit progress!\n");
    }
    return ret;
}

eReturnValues run_Format_Unit(const tDevice* device, runFormatUnitParameters formatParameters, bool pollForProgress)
{
    eReturnValues ret                   = SUCCESS;
    uint8_t*      dataBuf               = M_NULLPTR;
    uint32_t      dataSize              = UINT32_C(4); // assume short list for now
    bool          longList              = false;
    bool          fastFormatSupported   = false; // not used yet - TJE
    bool          formatSupported       = is_Format_Unit_Supported(device, &fastFormatSupported);
    uint8_t       fmtpInfo              = UINT8_C(0); // set protection information to zero
    uint8_t       defectListFormat      = UINT8_C(0);
    bool          initializationPattern = false;
    uint32_t      offset                = UINT32_C(2); // for filling in parameter data
    uint8_t       patternType           = UINT8_C(0);
    // validate the input parameters first
    // start with flags in the cdb
    if (!formatParameters.currentBlockSize && formatParameters.newBlockSize == 0)
    {
        return BAD_PARAMETER;
    }
    if (formatParameters.gList && formatParameters.glistSize == 0)
    {
        return BAD_PARAMETER;
    }
    if (formatParameters.gList)
    {
        defectListFormat = formatParameters.gList[1] & 0x03;
        if (formatParameters.glistSize > 0xFFFF && !longList)
        {
            dataSize += 4;
            longList = true;
            offset   = 4;
        }
        dataSize += formatParameters.glistSize;
    }
    if (!formatParameters.defaultFormat && (formatParameters.pattern || formatParameters.securityInitialize))
    {
        initializationPattern = true;
        dataSize += 4;
        if (formatParameters.pattern)
        {
            patternType = 1;
            dataSize += formatParameters.patternLength;
        }
        else
        {
            formatParameters.patternLength = 0;
        }
    }
    // check if format is supported
    if (!formatSupported || formatParameters.protectionType > 3)
    {
        return NOT_SUPPORTED;
    }
    if ((formatParameters.protectionType == 2 || formatParameters.protectionType == 3) &&
        formatParameters.protectionIntervalExponent != 0)
    {
        dataSize += 4;
        longList = true;
        offset   = 4;
    }
    // dataSize += 1;//adding 1 to make sure we don't go over the end of out memory
    // allocate memory
    dataBuf = C_CAST(
        uint8_t*, safe_calloc_aligned(dataSize * sizeof(uint8_t), sizeof(uint8_t), device->os_info.minimumAlignment));
    if (dataBuf == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    // now flags that go in parameter data
    // bytes 0 & 1 are the same between short and long parameter headers
    dataBuf[0] = 0; // no protection field stuff to set for now
    if (!formatParameters.disableImmediate)
    {
        dataBuf[1] = BIT1; // immediate bit
    }
    if (!formatParameters.defaultFormat)
    {
        dataBuf[1] |= BIT7; // set FOV bit
        if (formatParameters.disableCertification)
        {
            dataBuf[1] |= BIT5; // dcrt bit
        }
        if (formatParameters.disablePrimaryList)
        {
            dataBuf[1] |= BIT6; // dpry bit
        }
        if (formatParameters.stopOnListError)
        {
            dataBuf[1] |= BIT4; // stpf bit
        }
        if (initializationPattern)
        {
            dataBuf[1] |= BIT3; // ip bit
        }
    }
    if (!formatParameters.changeProtectionType)
    {
        formatParameters.protectionType             = device->drive_info.currentProtectionType;
        formatParameters.protectionIntervalExponent = device->drive_info.piExponent;
    }
    if (!(formatParameters.defaultFormat &&
          formatParameters.disableImmediate)) // Only set the fmtpInfo bit if we are sending data to the drive via
                                              // parameter list...otherwise this is an illegal combination
    {
        // set up protection fields
        switch (formatParameters.protectionType)
        {
        case 1:           // type 1
            fmtpInfo = 2; // 01b
            // protection field usage should be left zero
            break;
        case 2:           // type 2
            fmtpInfo = 3; // 11b
            // protection field usage should be left zero
            break;
        case 3:                 // type 3
            fmtpInfo = 3;       // 11b
            dataBuf[1] |= BIT0; // 001b
            break;
        case 0:  // no protection
        default: // don't set protection. There is a condition before this that should catch this error.
            fmtpInfo = 0;
            break;
        }
    }
    if (longList)
    {
        if ((formatParameters.protectionType == 2 || formatParameters.protectionType == 3) &&
            formatParameters.protectionIntervalExponent != 0)
        {
            // P_I_Information should be left as zero always! the exponent is in the lower nibble of this same byte
            dataBuf[3] = M_Nibble0(formatParameters.protectionIntervalExponent);
        }
        // set defect list length
        dataBuf[offset]     = M_Byte3(formatParameters.glistSize);
        dataBuf[offset + 1] = M_Byte2(formatParameters.glistSize);
        dataBuf[offset + 2] = M_Byte1(formatParameters.glistSize);
        dataBuf[offset + 3] = M_Byte0(formatParameters.glistSize);
        offset += 4;
    }
    else
    {
        dataBuf[offset]     = M_Byte1(formatParameters.glistSize);
        dataBuf[offset + 1] = M_Byte0(formatParameters.glistSize);
        offset += 2;
    }
    if (initializationPattern)
    {
        if (formatParameters.securityInitialize)
        {
            dataBuf[offset] |= BIT5;
        }
        dataBuf[offset + 1] = patternType;
        dataBuf[offset + 2] = M_Byte1(formatParameters.patternLength);
        dataBuf[offset + 3] = M_Byte0(formatParameters.patternLength);
        offset += 4;
        if (formatParameters.pattern && formatParameters.patternLength > 0)
        {
            // copy pattern into buffer
            safe_memcpy(&dataBuf[offset], dataSize - offset, formatParameters.pattern, formatParameters.patternLength);
            offset += formatParameters.patternLength;
        }
    }
    if (formatParameters.gList)
    {
        safe_memcpy(&dataBuf[offset], dataSize - offset, formatParameters.gList, formatParameters.glistSize);
        offset += formatParameters.glistSize;
    }
    dataSize = offset;
    // if they want to change the sector size, we need to do a mode select command
    if (!formatParameters.currentBlockSize || formatParameters.newMaxLBA)
    {
        modifyScsiBlkDescFields modifications;
        modifyScsiBlkDescFields results;
        safe_memset(&modifications, sizeof(modifyScsiBlkDescFields), 0, sizeof(modifyScsiBlkDescFields));
        safe_memset(&results, sizeof(modifyScsiBlkDescFields), 0, sizeof(modifyScsiBlkDescFields));
        if (formatParameters.newMaxLBA)
        {
            modifications.modifyNumBlocks       = true;
            modifications.numberOfLogicalBlocks = formatParameters.newMaxLBA;
        }
        if (!formatParameters.currentBlockSize)
        {
            modifications.modifyBlockLen     = true;
            modifications.logicalBlockLength = formatParameters.newBlockSize;
            if (!formatParameters.newMaxLBA)
            {
                // When changing the sector size, need to let the drive know to reset the maxLBA to a new value as well!
                modifications.modifyNumBlocks       = true;
                modifications.numberOfLogicalBlocks = UINT64_MAX;
            }
        }
        ret = modify_SCSI_Block_Descriptor(device, modifications, &results);
        if (ret == NOT_SUPPORTED)
        {
            // Determine what was not accepted. It's possible maxlba or blocksize were not accepted by the device.
            // Other errors like FAILURE or COMMAND_FAILURE from this function indicate other device issues, whereas
            // NOT_SUPPORTED means that the change requested did not happen.
            if (formatParameters.newMaxLBA)
            {
                if (formatParameters.newMaxLBA != results.numberOfLogicalBlocks + UINT64_C(1))
                {
                    print_str("WARNING: Changing maxlba during format is not supported.\n");
                    print_str("         Please try changing maxLBA once the format is completed\n");
                    print_str("         Format will continue running with all other options.\n");
                    ret = SUCCESS;
                }
            }
            if (!formatParameters.currentBlockSize)
            {
                // check what happened when requesting the block size change.
                printf("The requested block size is not supported: %" PRIu32 "\n", modifications.logicalBlockLength);
                printf("The device set the block size to %" PRIu32 " during this request.\n",
                       results.logicalBlockLength);
                print_str("Formatting the drive must continue in order to bring it out of the\n");
                print_str("format corrupt state.\n");
                if (results.logicalBlockLength % 512)
                {
                    modifications.logicalBlockLength =
                        uint32_round_down_power2(device->drive_info.deviceBlockSize, 512);
                    if (!formatParameters.newMaxLBA)
                    {
                        // When changing the sector size, need to let the drive know to reset the maxLBA to a new value
                        // as well!
                        modifications.modifyNumBlocks       = true;
                        modifications.numberOfLogicalBlocks = UINT64_MAX;
                    }
                    print_str("Since this new block size is not a multiple of 512 (a common supported sector size),\n");
                    printf("the sector size will be adjusted to %" PRIu32 " for maximum compatibility.\n",
                           modifications.logicalBlockLength);
                    ret = modify_SCSI_Block_Descriptor(device, modifications, &results);
                }
                ret = SUCCESS;
            }
        }
    }
    if (ret == SUCCESS)
    {
        uint32_t formatCommandTimeout = DEFAULT_COMMAND_TIMEOUT;
        if (formatParameters.disableImmediate)
        {
            if (os_Is_Infinite_Timeout_Supported())
            {
                formatCommandTimeout = INFINITE_TIMEOUT_VALUE;
            }
            else
            {
                formatCommandTimeout = MAX_CMD_TIMEOUT_SECONDS;
            }
        }
        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
        {
            print_str("Performing SCSI drive format.\n");
            print_str("Depending on the format request, this could take minutes to hours or days.\n");
            print_str("Do not remove power or attempt other access as interrupting it may make\n");
            print_str("the drive unusable or require performing this command again!!\n");
        }
        // send the format command
        if (formatParameters.defaultFormat && formatParameters.disableImmediate)
        {
            ret = scsi_Format_Unit(device, fmtpInfo, longList, false, formatParameters.completeList, defectListFormat,
                                   0, M_NULLPTR, 0, C_CAST(uint8_t, formatParameters.formatType), formatCommandTimeout);
        }
        else
        {
            ret =
                scsi_Format_Unit(device, fmtpInfo, longList, true, formatParameters.completeList, defectListFormat, 0,
                                 dataBuf, dataSize, C_CAST(uint8_t, formatParameters.formatType), formatCommandTimeout);
        }

        // poll for progress
        if (pollForProgress && ret == SUCCESS && !formatParameters.disableImmediate)
        {
            double   progress         = 0.0;
            uint32_t delayTimeSeconds = UINT32_C(300);
            if (is_SSD(device))
            {
                delayTimeSeconds = 5;
            }
            switch (formatParameters.formatType)
            {
            case FORMAT_FAST_WRITE_NOT_REQUIRED:
            case FORMAT_FAST_WRITE_REQUIRED:
                delayTimeSeconds = 5;
                break;
            default:
                break;
            }
            delay_Seconds(2); // 2 second delay to make sure it starts (and on SSD this may be enough for it to finish
                              // immediately)
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                uint8_t seconds = UINT8_C(0);
                uint8_t minutes = UINT8_C(0);
                uint8_t hours   = UINT8_C(0);
                convert_Seconds_To_Displayable_Time(delayTimeSeconds, M_NULLPTR, M_NULLPTR, &hours, &minutes, &seconds);
                print_str("Progress will be updated every ");
                print_Time_To_Screen(M_NULLPTR, M_NULLPTR, &hours, &minutes, &seconds);
                print_str("\n");
            }
            bool printedWaitLongerWarning = false;
            while (IN_PROGRESS == get_Format_Progress(device, &progress))
            {
                if (VERBOSITY_QUIET < device->deviceVerbosity && !printedWaitLongerWarning)
                {
                    printf("\r\tPercent Complete: %0.02f%%", progress);
                    flush_stdout();
                    // add 0.005 to round up since this is what is happening in the %f print above (more or less) and
                    // we really don't need a call to round() to accomplish this. This is also simple enough and close
                    // enough to warn the user that the drive is not yet done with the format
                    if (progress + 0.005 >= 100.0)
                    {
                        print_str("\n\tWARNING: Even though progress reports 100%, the sense data indicates\n");
                        print_str("\t         that a format is still in progress! Please continue waiting\n");
                        print_str("\t         until the sense data no longer indicates that a format is\n");
                        print_str("\t         in progress!\n");
                        printedWaitLongerWarning = true;
                    }
                }
                delay_Seconds(delayTimeSeconds); // time set above
            }
            ret = get_Format_Progress(device, &progress);
            if (ret == SUCCESS && progress < 100.00)
            {
                if (VERBOSITY_QUIET < device->deviceVerbosity)
                {
                    print_str("\r\tPercent Complete: 100.00%\n");
                    flush_stdout();
                }
            }
            else if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_str("\n");
            }
            if (ret == SUCCESS && formatParameters.formatType == FORMAT_STD_FORMAT)
            {
                os_Update_File_System_Cache(device);
            }
        }
        else
        {
            // check if there was an invalid parameter field specifying the security initialize bit...if so, print a
            // message and return not supported - TJE
        }
    }
    safe_free_aligned(&dataBuf);
    return ret;
}

eReturnValues get_Format_Status(const tDevice* device, ptrFormatStatus formatStatus)
{
    eReturnValues ret = SUCCESS;

    if (device == M_NULLPTR || formatStatus == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    // Need to allocate enough memory to read all parameters (0 - 4)
    // SCSI_FORMAT_STATUS_LOG_PAGE_MAX_LENGTH = LOG_PAGE_HEADER_LENGTH (4)
    //   + SCSI_LOG_PARAMETER_HEADER_LENGTH + SCSI_LOG_PARAMETER_MAX_DATA_LENGTH (4+255) for param 0
    //   + SCSI_LOG_PARAMETER_HEADER_LENGTH + 8 (4+8) for params 1, 2, 3
    //   + SCSI_LOG_PARAMETER_HEADER_LENGTH + 4 (4+4) for param 4
    //   = 307 bytes (conservative; SBC-4 spec maximum is 60 bytes)
    uint8_t* formatStatusPage =
        M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(SCSI_FORMAT_STATUS_LOG_PAGE_MAX_LENGTH, sizeof(uint8_t),
                                                         device->os_info.minimumAlignment));
    if (formatStatusPage == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    if (SUCCESS == scsi_Log_Sense_Cmd(device, false, LPC_CUMULATIVE_VALUES, LP_FORMAT_STATUS_LOG_PAGE, 0, 0,
                                      formatStatusPage, SCSI_FORMAT_STATUS_LOG_PAGE_MAX_LENGTH))
    {
        // NOTE: Parameters will be all F's when the data is not available or new or the last format failed
        if (get_bit_range_uint8(formatStatusPage[0], 5, 0) == LP_FORMAT_STATUS_LOG_PAGE &&
            !(formatStatusPage[0] & BIT6) && formatStatusPage[0] & BIT7 &&
            formatStatusPage[1] == 0) // make sure we got the right page!
        {
            // got the data, so let's loop through it.
            uint16_t pageLength          = M_BytesTo2ByteValue(formatStatusPage[2], formatStatusPage[3]);
            uint32_t offset              = LOG_PAGE_HEADER_LENGTH; // start here to begin looking at the parameters
            uint8_t  parameterLength     = UINT8_C(0);
            bool     lastFormatUnitAllFs = false, grownDefectsDuringCertificationAllFs = false,
                 totalBlockReassignsDuringFormatAllFs = false, totalNewBlocksReassignedAllFs = false,
                 powerOnMinutesSinceLastFormatAllFs = false;
            for (; offset < (C_CAST(uint32_t, pageLength) + LOG_PAGE_HEADER_LENGTH) &&
                   offset < SCSI_FORMAT_STATUS_LOG_PAGE_MAX_LENGTH;
                 offset += C_CAST(uint32_t, parameterLength) + SCSI_LOG_PARAMETER_HEADER_LENGTH)
            {
                uint16_t parameterCode =
                    M_BytesTo2ByteValue(formatStatusPage[offset + 0], formatStatusPage[offset + 1]);
                parameterLength = formatStatusPage[offset + 3];
                if (parameterLength == 0)
                {
                    // stop the infinite loop before it would start
                    break;
                }
                switch (parameterCode)
                {
                case 0: // format data out
                    formatStatus->lastFormatParametersValid = true;
                    {
                        if (is_Buffer_All_ByteValue(&formatStatusPage[offset + 4], parameterLength, UINT8_C(0xFF)))
                        {
                            lastFormatUnitAllFs = true;
                        }
                        else
                        {
                            if (formatStatusPage[offset + 3] >= 4)
                            {
                                formatStatus->lastFormatData.protectionFieldUsage =
                                    get_bit_range_uint8(formatStatusPage[offset + 4], 2, 0);
                                formatStatus->lastFormatData.formatOptionsValid = formatStatusPage[offset + 5] & BIT7;
                                formatStatus->lastFormatData.disablePrimaryList = formatStatusPage[offset + 5] & BIT6;
                                formatStatus->lastFormatData.disableCertify     = formatStatusPage[offset + 5] & BIT5;
                                formatStatus->lastFormatData.stopFormat         = formatStatusPage[offset + 5] & BIT4;
                                formatStatus->lastFormatData.initializationPattern =
                                    formatStatusPage[offset + 5] & BIT3;
                                formatStatus->lastFormatData.obsoleteDisableSaveParameters =
                                    formatStatusPage[offset + 5] & BIT2;
                                formatStatus->lastFormatData.immediateResponse = formatStatusPage[offset + 5] & BIT1;
                                formatStatus->lastFormatData.vendorSpecific    = formatStatusPage[offset + 5] & BIT0;
                            }
                            switch (formatStatusPage[offset + 3]) // based on the length of the data, it may be a short
                                                                  // or long list...
                            {
                            case 4: // short list
                                formatStatus->lastFormatData.defectListLength =
                                    M_BytesTo2ByteValue(formatStatusPage[offset + 6], formatStatusPage[offset + 7]);
                                break;
                            case 8: // long list
                                formatStatus->lastFormatData.isLongList = true;
                                formatStatus->lastFormatData.defectListLength =
                                    M_BytesTo4ByteValue(formatStatusPage[offset + 8], formatStatusPage[offset + 9],
                                                        formatStatusPage[offset + 10], formatStatusPage[offset + 11]);
                                formatStatus->lastFormatData.p_i_information = M_Nibble1(formatStatusPage[offset + 7]);
                                formatStatus->lastFormatData.protectionIntervalExponent =
                                    M_Nibble0(formatStatusPage[offset + 7]);
                                break;
                            default: // unknown
                                break;
                            }
                        }
                    }
                    break;
                case 1: // grown defects during certification
                    formatStatus->grownDefectsDuringCertificationValid = true;
                    formatStatus->grownDefectsDuringCertification      = M_BytesTo8ByteValue(
                        formatStatusPage[offset + 4], formatStatusPage[offset + 5], formatStatusPage[offset + 6],
                        formatStatusPage[offset + 7], formatStatusPage[offset + 8], formatStatusPage[offset + 9],
                        formatStatusPage[offset + 10], formatStatusPage[offset + 11]);
                    if (formatStatus->grownDefectsDuringCertification == UINT64_MAX)
                    {
                        grownDefectsDuringCertificationAllFs               = true;
                        formatStatus->grownDefectsDuringCertificationValid = false;
                    }
                    break;
                case 2: // total blocks reassigned during format
                    formatStatus->totalBlockReassignsDuringFormatValid = true;
                    formatStatus->totalBlockReassignsDuringFormat      = M_BytesTo8ByteValue(
                        formatStatusPage[offset + 4], formatStatusPage[offset + 5], formatStatusPage[offset + 6],
                        formatStatusPage[offset + 7], formatStatusPage[offset + 8], formatStatusPage[offset + 9],
                        formatStatusPage[offset + 10], formatStatusPage[offset + 11]);
                    if (formatStatus->totalBlockReassignsDuringFormat == UINT64_MAX)
                    {
                        totalBlockReassignsDuringFormatAllFs               = true;
                        formatStatus->totalBlockReassignsDuringFormatValid = false;
                    }
                    break;
                case 3: // total new blocks reassigned
                    formatStatus->totalNewBlocksReassignedValid = true;
                    formatStatus->totalNewBlocksReassigned      = M_BytesTo8ByteValue(
                        formatStatusPage[offset + 4], formatStatusPage[offset + 5], formatStatusPage[offset + 6],
                        formatStatusPage[offset + 7], formatStatusPage[offset + 8], formatStatusPage[offset + 9],
                        formatStatusPage[offset + 10], formatStatusPage[offset + 11]);
                    if (formatStatus->totalNewBlocksReassigned == UINT64_MAX)
                    {
                        totalNewBlocksReassignedAllFs               = true;
                        formatStatus->totalNewBlocksReassignedValid = false;
                    }
                    break;
                case 4: // power on minutes since format
                    formatStatus->powerOnMinutesSinceFormatValid = true;
                    formatStatus->powerOnMinutesSinceFormat =
                        M_BytesTo4ByteValue(formatStatusPage[offset + 4], formatStatusPage[offset + 5],
                                            formatStatusPage[offset + 6], formatStatusPage[offset + 7]);
                    if (formatStatus->powerOnMinutesSinceFormat == UINT32_MAX)
                    {
                        powerOnMinutesSinceLastFormatAllFs           = true;
                        formatStatus->powerOnMinutesSinceFormatValid = false;
                    }
                    break;
                default:
                    break;
                }
            }
            formatStatus->formatParametersAllFs = false;
            if (lastFormatUnitAllFs && grownDefectsDuringCertificationAllFs && totalBlockReassignsDuringFormatAllFs &&
                totalNewBlocksReassignedAllFs && powerOnMinutesSinceLastFormatAllFs)
            {
                formatStatus->formatParametersAllFs = true;
            }
        }
        else
        {
            formatStatus->grownDefectsDuringCertificationValid = false;
            formatStatus->lastFormatParametersValid            = false;
            formatStatus->powerOnMinutesSinceFormatValid       = false;
            formatStatus->totalBlockReassignsDuringFormatValid = false;
            formatStatus->totalNewBlocksReassignedValid        = false;
            ret                                                = NOT_SUPPORTED;
        }
        safe_free_aligned(&formatStatusPage);
    }
    else
    {
        formatStatus->grownDefectsDuringCertificationValid = false;
        formatStatus->lastFormatParametersValid            = false;
        formatStatus->powerOnMinutesSinceFormatValid       = false;
        formatStatus->totalBlockReassignsDuringFormatValid = false;
        formatStatus->totalNewBlocksReassignedValid        = false;
        ret                                                = NOT_SUPPORTED;
    }
    return ret;
}

void show_Format_Status_Log(ptrFormatStatus formatStatus)
{

    if (formatStatus != M_NULLPTR)
    {
        print_str("Format Status:\n");
        if (!formatStatus->formatParametersAllFs)
        {
            if (formatStatus->lastFormatParametersValid)
            {
                print_str("The last format unit was performed with the following parameters:\n");
                printf("\tProtection Field Usage: %" PRIX8 "h\n", formatStatus->lastFormatData.protectionFieldUsage);
                if (formatStatus->lastFormatData.formatOptionsValid)
                {
                    print_str("\tFormat Options Valid\n");
                    if (formatStatus->lastFormatData.disablePrimaryList)
                    {
                        print_str("\tPrimary List Disabled\n");
                    }
                    if (formatStatus->lastFormatData.disableCertify)
                    {
                        print_str("\tCertification Disabled\n");
                    }
                    if (formatStatus->lastFormatData.stopFormat)
                    {
                        print_str("\tStop format on list error\n");
                    }
                    if (formatStatus->lastFormatData.initializationPattern)
                    {
                        print_str("\tInitialization Pattern provided\n");
                    }
                }
                else
                {
                    print_str("\tDefault format\n");
                }
                if (formatStatus->lastFormatData.obsoleteDisableSaveParameters)
                {
                    print_str("\tObsolete disable save parameters bit set\n");
                }
                if (formatStatus->lastFormatData.immediateResponse)
                {
                    print_str("\tImmediate Response Bit set\n");
                }
                if (formatStatus->lastFormatData.vendorSpecific)
                {
                    print_str("\tVendor Specific Bit set\n");
                }
                if (formatStatus->lastFormatData.isLongList)
                {
                    printf("\tP_I_Information: %" PRIX8 "h", formatStatus->lastFormatData.p_i_information);
                    printf("\tProtection Interval Exponent: %" PRIu8 "\n",
                           formatStatus->lastFormatData.protectionIntervalExponent);
                }
                printf("\tDefect List Length: %" PRIu32 "\n", formatStatus->lastFormatData.defectListLength);
            }
            if (formatStatus->grownDefectsDuringCertificationValid)
            {
                printf("Grown Defects During Certification: %" PRIu64 "\n",
                       formatStatus->grownDefectsDuringCertification);
            }
            if (formatStatus->totalBlockReassignsDuringFormatValid)
            {
                printf("Total Block Reassigns During Format: %" PRIu64 "\n",
                       formatStatus->totalBlockReassignsDuringFormat);
            }
            if (formatStatus->totalNewBlocksReassignedValid)
            {
                printf("Total New Blocks Reassigned: %" PRIu64 "\n", formatStatus->totalNewBlocksReassigned);
            }
            if (formatStatus->powerOnMinutesSinceFormatValid)
            {
                printf("Power On Minutes Since Last Format: %" PRIu32 "\n", formatStatus->powerOnMinutesSinceFormat);
                // convert the time to seconds, then print it in a displayable format
                print_str("Power On Time Since Last Format: ");
                uint16_t days    = UINT16_C(0);
                uint8_t  years   = UINT8_C(0);
                uint8_t  hours   = UINT8_C(0);
                uint8_t  minutes = UINT8_C(0);
                uint8_t  seconds = UINT8_C(0);
                convert_Seconds_To_Displayable_Time(C_CAST(uint64_t, formatStatus->powerOnMinutesSinceFormat) *
                                                        UINT64_C(60),
                                                    &years, &days, &hours, &minutes, &seconds);
                print_Time_To_Screen(&years, &days, &hours, &minutes, &seconds);
                print_str("\n");
            }
        }
        else
        {
            print_str("Format unit currently in progress or the last format command failed!\n");
        }
    }
}

bool is_Set_Sector_Configuration_Supported(const tDevice* device)
{
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, idDataLogSupportedCapabilities, LEGACY_DRIVE_SEC_SIZE);
        if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA,
                                                 ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES, idDataLogSupportedCapabilities,
                                                 LEGACY_DRIVE_SEC_SIZE, 0))
        {
            uint64_t qword0 = M_BytesTo8ByteValue(idDataLogSupportedCapabilities[7], idDataLogSupportedCapabilities[6],
                                                  idDataLogSupportedCapabilities[5], idDataLogSupportedCapabilities[4],
                                                  idDataLogSupportedCapabilities[3], idDataLogSupportedCapabilities[2],
                                                  idDataLogSupportedCapabilities[1], idDataLogSupportedCapabilities[0]);
            if (qword0 & BIT63 && M_Byte2(qword0) == ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES &&
                M_Word0(qword0) >= 0x0001)
            {
                uint64_t supportedCapabilitiesQWord =
                    M_BytesTo8ByteValue(idDataLogSupportedCapabilities[15], idDataLogSupportedCapabilities[14],
                                        idDataLogSupportedCapabilities[13], idDataLogSupportedCapabilities[12],
                                        idDataLogSupportedCapabilities[11], idDataLogSupportedCapabilities[10],
                                        idDataLogSupportedCapabilities[9], idDataLogSupportedCapabilities[8]);
                if (supportedCapabilitiesQWord & BIT63 &&
                    supportedCapabilitiesQWord &
                        BIT49) // check bit63 since it should always be 1, then bit 49 for set sector configuration
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        else
        {
            return false;
        }
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        bool fastFormatSupported = false;
        if (is_Format_Unit_Supported(device, &fastFormatSupported))
        {
            return fastFormatSupported;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
    return false;
}

uint32_t get_Number_Of_Supported_Sector_Sizes(M_ATTR_UNUSED const tDevice* device)
{
    // this function is obsolete now that there is a static size in the supported formats structure.
    // so this just needs to return 1
    return UINT32_C(1);
}

static bool is_ATA_At_Least_ACS_4(const tDevice* device)
{
    if (is_ATA_Identify_Word_Valid(device->drive_info.IdentifyData.ata.Word080))
    {
        // If bits 11 or higher are set to 1, then at least ACS-4 is supported which is when the
        // set sector configuration command was introduced.
        return M_ToBool(device->drive_info.IdentifyData.ata.Word080 & 0xF800);
    }
    else
    {
        return false;
    }
}

// this is a weird case where the drive may be in a corrupt state and cannot reliably report the supported
// sector sizes. We know that when this happens, we can still issue the command with a valid descriptor check
// value. These descriptor check values are unique to each vendor and the vendor can decide to change these at
// any point. They could change during firmware updates, or for different models, etc. At this time Seagate has
// these hard coded values so we will use these so that the command can be retired and potentially recover a
// drive that was interrupted while it was previously running a format operation.
static void set_Seagate_ATA_Format_Descriptors(ptrSupportedFormats formats)
{
    formats->sectorSizes[0].valid                     = true;
    formats->sectorSizes[0].currentFormat             = false;
    formats->sectorSizes[0].logicalBlockLength        = 512;
    formats->sectorSizes[0].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_ATA;
    formats->sectorSizes[0].ataSetSectorFields.descriptorCheck =
        0xF293; // hard coded value from Seagate for 512 byte sector size
    formats->sectorSizes[0].ataSetSectorFields.descriptorIndex = 0;
    formats->sectorSizes[1].valid                              = true;
    formats->sectorSizes[1].currentFormat                      = false;
    formats->sectorSizes[1].logicalBlockLength                 = 4096;
    formats->sectorSizes[1].additionalInformationType          = SECTOR_SIZE_ADDITIONAL_INFO_ATA;
    formats->sectorSizes[1].ataSetSectorFields.descriptorCheck =
        0xC8D7; // hard coded value from Seagate for 4096 byte sector size
    formats->sectorSizes[1].ataSetSectorFields.descriptorIndex = 1;
}

// Default case for drives that do not support changing sector size. Just reports back the current size.
static void set_Current_ATA_Format_Descriptor(ptrSupportedFormats formats, uint32_t currentBlockSize)
{
    formats->deviceSupportsOtherFormats                              = false;
    formats->numberOfSectorSizes                                     = 1;
    formats->protectionInformationSupported.deviceSupportsProtection = false;
    formats->sectorSizes[0].valid                                    = true;
    formats->sectorSizes[0].currentFormat                            = true;
    formats->sectorSizes[0].logicalBlockLength                       = currentBlockSize;
    formats->sectorSizes[0].additionalInformationType                = SECTOR_SIZE_ADDITIONAL_INFO_NONE;
}

static eReturnValues ata_Get_Supported_Formats(const tDevice* device, ptrSupportedFormats formats)
{
    eReturnValues ret      = NOT_SUPPORTED;
    uint32_t      log2fLen = UINT32_C(0);
    if (is_Set_Sector_Configuration_Supported(device) ||
        (SUCCESS == get_ATA_Log_Size(device, ATA_LOG_SECTOR_CONFIGURATION_LOG, &log2fLen, true, false) && log2fLen > 0))
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, sectorConfigurationLog, LEGACY_DRIVE_SEC_SIZE);
        if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_SECTOR_CONFIGURATION_LOG, 0, sectorConfigurationLog,
                                                 LEGACY_DRIVE_SEC_SIZE, 0))
        {
            formats->deviceSupportsOtherFormats                              = true;
            formats->protectionInformationSupported.deviceSupportsProtection = false;
            formats->numberOfSectorSizes                                     = UINT32_C(0);
            for (uint32_t iter = UINT32_C(0), sectorSizeCounter = UINT32_C(0);
                 iter < LEGACY_DRIVE_SEC_SIZE && sectorSizeCounter < UINT16_MAX &&
                 sectorSizeCounter < MAX_SECTOR_SIZES_ARRAY;
                 iter += UINT32_C(16), ++sectorSizeCounter)
            {
                formats->sectorSizes[sectorSizeCounter].logicalBlockLength =
                    M_BytesTo4ByteValue(sectorConfigurationLog[7 + iter], sectorConfigurationLog[6 + iter],
                                        sectorConfigurationLog[5 + iter], sectorConfigurationLog[4 + iter]) *
                    2;
                formats->sectorSizes[sectorSizeCounter].ataSetSectorFields.descriptorCheck =
                    M_BytesTo2ByteValue(sectorConfigurationLog[3 + iter], sectorConfigurationLog[2 + iter]);
                if (formats->sectorSizes[sectorSizeCounter].logicalBlockLength > 0 &&
                    formats->sectorSizes[sectorSizeCounter].ataSetSectorFields.descriptorCheck != 0)
                {
                    formats->sectorSizes[sectorSizeCounter].valid                     = true;
                    formats->sectorSizes[sectorSizeCounter].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_ATA;
                    // special case for USB attached ATA drives to set the indicator for the current configuration.
                    // Since this is done as a SAT passthrough command on USB, we need to check the child drive
                    // information rather than the primary information.
                    if (((device->drive_info.bridge_info.isValid &&
                          formats->sectorSizes[sectorSizeCounter].logicalBlockLength ==
                              device->drive_info.bridge_info.childDeviceBlockSize)) ||
                        (formats->sectorSizes[sectorSizeCounter].logicalBlockLength ==
                         device->drive_info.deviceBlockSize))
                    {
                        formats->sectorSizes[sectorSizeCounter].currentFormat = true;
                    }
                    ++(formats->numberOfSectorSizes);
                }
                formats->sectorSizes[sectorSizeCounter].ataSetSectorFields.descriptorIndex = C_CAST(uint8_t, iter / 16);
            }
            ret = SUCCESS;
        }
        else
        {
            ret = FAILURE;
        }
    }
    if (ret != SUCCESS)
    {
        if (SEAGATE == is_Seagate_Family(device) && is_SSD(device) == false && is_ATA_At_Least_ACS_4(device))
        {
            set_Seagate_ATA_Format_Descriptors(formats);
            ret = SUCCESS;
        }
        else
        {
            // Default case for drives that do not support changing sector size. Just reports back the current size.
            set_Current_ATA_Format_Descriptor(formats, device->drive_info.deviceBlockSize);
            ret = SUCCESS;
        }
    }
    return ret;
}

static eReturnValues scsi_Get_Supported_Formats(const tDevice* device, ptrSupportedFormats formats)
{
    eReturnValues ret         = NOT_SUPPORTED;
    uint8_t*      inquiryData = C_CAST(
        uint8_t*, safe_calloc_aligned(INQ_RETURN_DATA_LENGTH, sizeof(uint8_t), device->os_info.minimumAlignment));
    if (inquiryData == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    if (SUCCESS == scsi_Inquiry(device, inquiryData, INQ_RETURN_DATA_LENGTH, 0, false, false))
    {
        ret = SUCCESS;
        if (inquiryData[5] & BIT0)
        {
            formats->protectionInformationSupported.deviceSupportsProtection = true;
            // now read the extended inquiry data VPD page
            if (SUCCESS ==
                scsi_Inquiry(device, inquiryData, INQ_RETURN_DATA_LENGTH, EXTENDED_INQUIRY_DATA, true, false))
            {
                switch (inquiryData[0] & 0x1F)
                {
                case 0:                                   // direct access block device
                    switch ((inquiryData[4] >> 3) & 0x07) // spt field
                    {
                    case 0:
                        formats->protectionInformationSupported.protectionType1Supported = true;
                        formats->protectionInformationSupported.protectionType2Supported = false;
                        formats->protectionInformationSupported.protectionType3Supported = false;
                        break;
                    case 1:
                        formats->protectionInformationSupported.protectionType1Supported = true;
                        formats->protectionInformationSupported.protectionType2Supported = true;
                        formats->protectionInformationSupported.protectionType3Supported = false;
                        break;
                    case 2:
                        formats->protectionInformationSupported.protectionType1Supported = false;
                        formats->protectionInformationSupported.protectionType2Supported = true;
                        formats->protectionInformationSupported.protectionType3Supported = false;
                        break;
                    case 3:
                        formats->protectionInformationSupported.protectionType1Supported = true;
                        formats->protectionInformationSupported.protectionType2Supported = false;
                        formats->protectionInformationSupported.protectionType3Supported = true;
                        break;
                    case 4:
                        formats->protectionInformationSupported.protectionType1Supported = false;
                        formats->protectionInformationSupported.protectionType2Supported = false;
                        formats->protectionInformationSupported.protectionType3Supported = true;
                        break;
                    case 5:
                        formats->protectionInformationSupported.protectionType1Supported = false;
                        formats->protectionInformationSupported.protectionType2Supported = true;
                        formats->protectionInformationSupported.protectionType3Supported = true;
                        break;
                    case 6:
                        formats->protectionInformationSupported.protectionReportedPerSectorSize = true;
                        break;
                    case 7:
                        formats->protectionInformationSupported.protectionType1Supported = true;
                        formats->protectionInformationSupported.protectionType2Supported = true;
                        formats->protectionInformationSupported.protectionType3Supported = true;
                        break;
                    }
                    break;
                case 1: // sequential access block device (we don't care...it's a tape drive...)
                default:
                    break;
                }
            }
        }
    }
    safe_free_aligned(&inquiryData);
    bool     dummyUpCommonSizes             = true;
    uint32_t supportedSectorSizesDataLength = UINT32_C(0);
    get_SCSI_VPD_Page_Size(device, SUPPORTED_BLOCK_LENGTHS_AND_PROTECTION_TYPES, &supportedSectorSizesDataLength);
    if (formats->protectionInformationSupported.protectionReportedPerSectorSize || supportedSectorSizesDataLength)
    {
        uint8_t* supportedBlockLengthsData =
            M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(supportedSectorSizesDataLength, sizeof(uint8_t),
                                                             device->os_info.minimumAlignment));
        if (supportedBlockLengthsData == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        if (SUCCESS == get_SCSI_VPD(device, SUPPORTED_BLOCK_LENGTHS_AND_PROTECTION_TYPES, M_NULLPTR, M_NULLPTR, true,
                                    supportedBlockLengthsData, supportedSectorSizesDataLength, M_NULLPTR))
        {
            dummyUpCommonSizes           = false;
            formats->numberOfSectorSizes = 0;
            for (uint32_t iter = UINT32_C(4), sectorSizeCounter = UINT32_C(0);
                 (iter + UINT32_C(8)) < supportedSectorSizesDataLength && sectorSizeCounter < MAX_SECTOR_SIZES_ARRAY;
                 iter += UINT32_C(8), ++sectorSizeCounter, ++formats->numberOfSectorSizes)
            {
                formats->sectorSizes[sectorSizeCounter].valid = true;
                formats->sectorSizes[sectorSizeCounter].logicalBlockLength =
                    M_BytesTo4ByteValue(supportedBlockLengthsData[iter + 0], supportedBlockLengthsData[iter + 1],
                                        supportedBlockLengthsData[iter + 2], supportedBlockLengthsData[iter + 3]);
                formats->sectorSizes[sectorSizeCounter].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.piSupportBitsValid = true;
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.p_i_i_sup =
                    M_ToBool(supportedBlockLengthsData[iter + 4] & BIT6);
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.no_pi_chk =
                    M_ToBool(supportedBlockLengthsData[iter + 4] & BIT3);
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.grd_chk =
                    M_ToBool(supportedBlockLengthsData[iter + 4] & BIT2);
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.app_chk =
                    M_ToBool(supportedBlockLengthsData[iter + 4] & BIT1);
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.ref_chk =
                    M_ToBool(supportedBlockLengthsData[iter + 5] & BIT0);
                formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t0ps =
                    M_ToBool(supportedBlockLengthsData[iter + 5] & BIT0);
                if (supportedBlockLengthsData[iter + 5] & BIT1)
                {
                    formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t1ps      = true;
                    formats->protectionInformationSupported.protectionType1Supported = true;
                }
                if (supportedBlockLengthsData[iter + 5] & BIT2)
                {
                    formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t2ps      = true;
                    formats->protectionInformationSupported.protectionType2Supported = true;
                }
                if (supportedBlockLengthsData[iter + 5] & BIT3)
                {
                    formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t3ps      = true;
                    formats->protectionInformationSupported.protectionType3Supported = true;
                }
                if (device->drive_info.deviceBlockSize == formats->sectorSizes[sectorSizeCounter].logicalBlockLength)
                {
                    // check PI to see if this is the current format
                    switch (device->drive_info.currentProtectionType)
                    {
                    case 0:
                        if (formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t0ps)
                        {
                            formats->sectorSizes[sectorSizeCounter].currentFormat = true;
                        }
                        break;
                    case 1:
                        if (formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t1ps)
                        {
                            formats->sectorSizes[sectorSizeCounter].currentFormat = true;
                        }
                        break;
                    case 2:
                        if (formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t2ps)
                        {
                            formats->sectorSizes[sectorSizeCounter].currentFormat = true;
                        }
                        break;
                    case 3:
                        if (formats->sectorSizes[sectorSizeCounter].scsiSectorBits.t3ps)
                        {
                            formats->sectorSizes[sectorSizeCounter].currentFormat = true;
                        }
                        break;
                    default:
                        break;
                    }
                }
            }
            if (formats->protectionInformationSupported.protectionType1Supported ||
                formats->protectionInformationSupported.protectionType2Supported ||
                formats->protectionInformationSupported.protectionType3Supported)
            {
                formats->protectionInformationSupported.deviceSupportsProtection = true;
            }
            ret = SUCCESS;
        }
        safe_free_aligned(&supportedBlockLengthsData);
    }
    if (is_Format_Unit_Supported(device, &formats->scsiFastFormatSupported))
    {
        if (dummyUpCommonSizes)
        {
            formats->scsiInformationNotReported = true;
            ret                                 = SUCCESS;
            // dummy up the support based on what is known from traditional formatting support - don't include any PI
            // stuff here for now. Need more refactoring
            enum eSCSIEnterpriseSectorSizesOffset
            {
                SCSI_ENT_SECT_SIZE_512  = 0,
                SCSI_ENT_SECT_SIZE_520  = 1,
                SCSI_ENT_SECT_SIZE_524  = 2,
                SCSI_ENT_SECT_SIZE_528  = 3,
                SCSI_ENT_SECT_SIZE_4096 = 4,
                SCSI_ENT_SECT_SIZE_4160 = 5,
                SCSI_ENT_SECT_SIZE_4192 = 6,
                SCSI_ENT_SECT_SIZE_4224 = 7
            };
#define MAX_SCSI_ENTERPRISE_SECTOR_SIZES (8)
            enum eSCSIEnterpriseSectorSizes
            {
                SEC_SIZE_512  = 512,
                SEC_SIZE_520  = 520,
                SEC_SIZE_524  = 524,
                SEC_SIZE_528  = 528,
                SEC_SIZE_4096 = 4096,
                SEC_SIZE_4160 = 4160,
                SEC_SIZE_4192 = 4192,
                SEC_SIZE_4224 = 4224
            };
            formats->numberOfSectorSizes                                           = MAX_SCSI_ENTERPRISE_SECTOR_SIZES;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_512].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_512].logicalBlockLength        = SEC_SIZE_512;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_512].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_520].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_520].logicalBlockLength        = SEC_SIZE_520;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_520].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_524].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_524].logicalBlockLength        = SEC_SIZE_524;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_524].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_528].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_528].logicalBlockLength        = SEC_SIZE_528;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_528].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096].logicalBlockLength        = SEC_SIZE_4096;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4160].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4160].logicalBlockLength        = SEC_SIZE_4160;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4160].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4192].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4192].logicalBlockLength        = SEC_SIZE_4192;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4192].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4224].valid                     = true;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4224].logicalBlockLength        = SEC_SIZE_4224;
            formats->sectorSizes[SCSI_ENT_SECT_SIZE_4224].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_SCSI;

            switch (device->drive_info.deviceBlockSize)
            {
            case SEC_SIZE_512:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_512].currentFormat = true;
                break;
            case SEC_SIZE_520:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_520].currentFormat = true;
                break;
            case SEC_SIZE_524:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_524].currentFormat = true;
                break;
            case SEC_SIZE_528:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_528].currentFormat = true;
                break;
            case SEC_SIZE_4096:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096].currentFormat = true;
                break;
            case SEC_SIZE_4160:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_4160].currentFormat = true;
                break;
            case SEC_SIZE_4192:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_4192].currentFormat = true;
                break;
            case SEC_SIZE_4224:
                formats->sectorSizes[SCSI_ENT_SECT_SIZE_4224].currentFormat = true;
                break;
            default:
                break;
            }
            if (!formats->scsiFastFormatSupported)
            {
                formats->numberOfSectorSizes /=
                    2; // without fast format support, number of supported sizes is cut in half
                // dummy up based on current sector size
                if (device->drive_info.deviceBlockSize < SEC_SIZE_4096)
                {
                    // memset away the 4k sizes
                    safe_memset(&formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096],
                                sizeof(sectorSize) * formats->numberOfSectorSizes, 0,
                                sizeof(sectorSize) * formats->numberOfSectorSizes);
                }
                else
                {
                    // move 4k sizes to front, removing 5xx sizes
                    safe_memmove(&formats->sectorSizes[0], sizeof(sectorSize) * formats->numberOfSectorSizes,
                                 &formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096],
                                 sizeof(sectorSize) * formats->numberOfSectorSizes);
                    // now memset away old stuff so it doesn't look duplicated
                    safe_memset(&formats->sectorSizes[SCSI_ENT_SECT_SIZE_4096],
                                sizeof(sectorSize) * formats->numberOfSectorSizes, 0,
                                sizeof(sectorSize) * formats->numberOfSectorSizes);
                }
            }
        }
    }
    else
    {
        formats->deviceSupportsOtherFormats               = false;
        formats->numberOfSectorSizes                      = 1;
        formats->sectorSizes[0].valid                     = true;
        formats->sectorSizes[0].currentFormat             = true;
        formats->sectorSizes[0].logicalBlockLength        = device->drive_info.deviceBlockSize;
        formats->sectorSizes[0].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_NONE;
    }

    return ret;
}

static eReturnValues nvme_Get_Supported_Formats(const tDevice* device, ptrSupportedFormats formats)
{
    // read the PI support from identify namespace structure
    if (device->drive_info.IdentifyData.nvme.ns.dpc > 0)
    {
        if (device->drive_info.IdentifyData.nvme.ns.dpc & BIT0)
        {
            formats->protectionInformationSupported.protectionType1Supported = true;
        }
        else
        {
            formats->protectionInformationSupported.protectionType1Supported = false;
        }
        if (device->drive_info.IdentifyData.nvme.ns.dpc & BIT1)
        {
            formats->protectionInformationSupported.protectionType2Supported = true;
        }
        else
        {
            formats->protectionInformationSupported.protectionType2Supported = false;
        }
        if (device->drive_info.IdentifyData.nvme.ns.dpc & BIT2)
        {
            formats->protectionInformationSupported.protectionType3Supported = true;
        }
        else
        {
            formats->protectionInformationSupported.protectionType3Supported = false;
        }
        if (formats->protectionInformationSupported.protectionType1Supported ||
            formats->protectionInformationSupported.protectionType2Supported ||
            formats->protectionInformationSupported.protectionType3Supported)
        {
            formats->protectionInformationSupported.deviceSupportsProtection = true;
        }
        else
        {
            formats->protectionInformationSupported.deviceSupportsProtection = false;
        }
        formats->protectionInformationSupported.nvmSpecificPI.nvmSpecificValid = true;
        formats->protectionInformationSupported.nvmSpecificPI.piFirst8 =
            device->drive_info.IdentifyData.nvme.ns.dpc & BIT3;
        formats->protectionInformationSupported.nvmSpecificPI.piLast8 =
            device->drive_info.IdentifyData.nvme.ns.dpc & BIT4;
    }
    formats->nvmeMetadataSupport.nvmSpecificValid    = true;
    formats->nvmeMetadataSupport.metadataXLBASup     = device->drive_info.IdentifyData.nvme.ns.mc & BIT0;
    formats->nvmeMetadataSupport.metadataSeparateSup = device->drive_info.IdentifyData.nvme.ns.mc & BIT1;

    formats->deviceSupportsOtherFormats = true;
    formats->numberOfSectorSizes        = 0; // clear this out before we set it to something below
    // set metadata and PI location bits first
    for (uint8_t iter = UINT8_C(0);
         iter < NVME_0_BASED(device->drive_info.IdentifyData.nvme.ns.nlbaf) && iter < MAX_SECTOR_SIZES_ARRAY; ++iter)
    {
        if (device->drive_info.IdentifyData.nvme.ns.lbaf[iter].lbaDS > 0)
        {
            formats->sectorSizes[iter].valid = true;
            formats->sectorSizes[iter].logicalBlockLength =
                C_CAST(uint32_t, power_Of_Two(device->drive_info.IdentifyData.nvme.ns.lbaf[iter].lbaDS));
            formats->sectorSizes[iter].additionalInformationType = SECTOR_SIZE_ADDITIONAL_INFO_NVME;
            formats->sectorSizes[iter].nvmeSectorBits.relativePerformance =
                get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.lbaf[iter].rp, 1, 0);
            formats->sectorSizes[iter].nvmeSectorBits.metadataSize =
                le16_to_host(device->drive_info.IdentifyData.nvme.ns.lbaf[iter].ms);
            ++formats->numberOfSectorSizes;
        }
    }

    uint8_t flbas = get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.flbas, 3, 0);
    if (NVME_0_BASED(device->drive_info.IdentifyData.nvme.ns.nlbaf) > 16)
    {
        // need to append 2 more bits to interpret this correctly since number of formats > 16
        flbas |= get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.flbas, 6, 5) << 4;
    }

    // This should not happen or trigger on a real valid device, but adding this to assist in debugging too.
    assert(flbas < formats->numberOfSectorSizes && "Current LBA format out of range for number of device reported "
                                                   "formats. Drive bug or malicious device detected.");

    // Max formats in NVMe is 64 which is the same as this value, so it *should never* go out of bounds.
    if (flbas < MAX_SECTOR_SIZES_ARRAY && flbas < formats->numberOfSectorSizes)
        M_LIKELY
        {
            // set current format
            formats->sectorSizes[flbas].currentFormat = true;
        }
    return SUCCESS;
}

eReturnValues get_Supported_Formats(const tDevice* device, ptrSupportedFormats formats)
{
    eReturnValues ret = NOT_SUPPORTED;

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

    switch (device->drive_info.drive_type)
    {
    case ATA_DRIVE:
        return ata_Get_Supported_Formats(device, formats);
    case NVME_DRIVE:
        return nvme_Get_Supported_Formats(device, formats);
    case SCSI_DRIVE:
        return scsi_Get_Supported_Formats(device, formats);
    default:
        break;
    }
    return ret;
}

void show_Supported_Formats(ptrSupportedFormats formats)
{
    print_str("\nSupported Logical Block Sizes and Protection Types:\n");
    print_str("---------------------------------------------------\n");
    print_str("  * - current device format\n");
    print_str("PI Key:\n");
    print_str("  Y - protection type supported at specified block size\n");
    print_str("  N - protection type not supported at specified block size\n");
    print_str("  ? - unable to determine support for protection type at specified block size\n");
    print_str("Relative performance key:\n");
    print_str("  N/A - relative performance not available.\n");
    print_str("  Best    \n");
    print_str("  Better  \n");
    print_str("  Good    \n");
    print_str("  Degraded\n");
    // now print out the supported block sizes
    print_str("--------------------------------------------------------------------------------\n");
    printf(" %18s  %4s  %4s  %4s  %4s  %20s  %13s\n", "Logical Block Size", "PI-0", "PI-1", "PI-2", "PI-3",
           "Relative Performance", "Metadata Size");
    print_str("--------------------------------------------------------------------------------\n");
    for (uint32_t iter = UINT32_C(0); iter < formats->numberOfSectorSizes && iter < MAX_SECTOR_SIZES_ARRAY; ++iter)
    {
        if (formats->sectorSizes[iter].valid)
        {
            char current = ' ';
            char pi0     = 0;
            char pi1     = 0;
            char pi2     = 0;
            char pi3     = 0;
#define PERF_STRING_SIZE 10
#define META_STRING_SIZE 10
            DECLARE_ZERO_INIT_ARRAY(char, perf, PERF_STRING_SIZE);
            DECLARE_ZERO_INIT_ARRAY(char, metaSize, META_STRING_SIZE);
            snprintf_err_handle(perf, PERF_STRING_SIZE, "N/A");
            snprintf_err_handle(metaSize, META_STRING_SIZE, "N/A");
            if (formats->protectionInformationSupported.deviceSupportsProtection)
            {
                pi0 = 'Y';
                if (formats->protectionInformationSupported.protectionType1Supported)
                {
                    pi1 = '?';
                }
                else
                {
                    pi1 = 'N';
                }
                if (formats->protectionInformationSupported.protectionType2Supported)
                {
                    pi2 = '?';
                }
                else
                {
                    pi2 = 'N';
                }
                if (formats->protectionInformationSupported.protectionType3Supported)
                {
                    pi3 = '?';
                }
                else
                {
                    pi3 = 'N';
                }
            }
            else if (formats->protectionInformationSupported.protectionReportedPerSectorSize)
            {
                switch (formats->sectorSizes[iter].additionalInformationType)
                {
                case SECTOR_SIZE_ADDITIONAL_INFO_SCSI:
                    if (formats->sectorSizes[iter].scsiSectorBits.piSupportBitsValid)
                    {
                        if (formats->sectorSizes[iter].scsiSectorBits.t0ps)
                        {
                            pi0 = 'Y';
                        }
                        else
                        {
                            pi0 = 'N';
                        }
                        if (formats->sectorSizes[iter].scsiSectorBits.t1ps)
                        {
                            pi1 = 'Y';
                        }
                        else
                        {
                            pi1 = 'N';
                        }
                        if (formats->sectorSizes[iter].scsiSectorBits.t2ps)
                        {
                            pi2 = 'Y';
                        }
                        else
                        {
                            pi2 = 'N';
                        }
                        if (formats->sectorSizes[iter].scsiSectorBits.t3ps)
                        {
                            pi3 = 'Y';
                        }
                        else
                        {
                            pi3 = 'N';
                        }
                    }
                    break;
                default:
                    break;
                }
            }
            else
            {
                pi0 = 'Y';
                pi1 = 'N';
                pi2 = 'N';
                pi3 = 'N';
            }
            // set relative performance for NVMe
            switch (formats->sectorSizes[iter].additionalInformationType)
            {
            case SECTOR_SIZE_ADDITIONAL_INFO_NVME:
                safe_memset(perf, PERF_STRING_SIZE, 0, PERF_STRING_SIZE);
                switch (formats->sectorSizes[iter].nvmeSectorBits.relativePerformance)
                {
                case 0:
                    snprintf_err_handle(perf, PERF_STRING_SIZE, "Best");
                    break;
                case 1:
                    snprintf_err_handle(perf, PERF_STRING_SIZE, "Better");
                    break;
                case 2:
                    snprintf_err_handle(perf, PERF_STRING_SIZE, "Good");
                    break;
                case 3:
                    snprintf_err_handle(perf, PERF_STRING_SIZE, "Degraded");
                    break;
                default:
                    snprintf_err_handle(perf, PERF_STRING_SIZE, "N/A");
                    break;
                }
                safe_memset(metaSize, META_STRING_SIZE, 0, META_STRING_SIZE);
                snprintf_err_handle(metaSize, META_STRING_SIZE, "%" PRIu16,
                                    formats->sectorSizes[iter].nvmeSectorBits.metadataSize);
                break;
            default:
                break;
            }
            if (formats->sectorSizes[iter].currentFormat)
            {
                current = '*';
            }
            printf("%c%18" PRIu32 "  %4c  %4c  %4c  %4c  %20s  %13s\n", current,
                   formats->sectorSizes[iter].logicalBlockLength, pi0, pi1, pi2, pi3, perf, metaSize);
        }
    }
    print_str("--------------------------------------------------------------------------------\n");
    if (formats->scsiInformationNotReported)
    {
        print_str("NOTE: Device is not capable of showing all sizes it supports. Only common\n");
        print_str("      sizes are listed. Please consult the product manual for all supported\n");
        print_str("      combinations.\n");
    }
    if (formats->protectionInformationSupported.deviceSupportsProtection)
    {
        print_str("NOTE: This device supports protection information (PI) (a.k.a. End to End protection).\n");
        print_str("\tType 0 - No protection beyond transport protocol\n"); // this is always supported
        if (formats->protectionInformationSupported.protectionType1Supported)
        {
            print_str("\tType 1 - Logical Block Guard and Logical Block Reference Tag\n");
        }
        if (formats->protectionInformationSupported.protectionType2Supported)
        {
            print_str(
                "\tType 2 - Logical Block Guard and Logical Block Reference Tag (except first block)\n\t\t 32byte "
                "read/write CDBs allowed\n");
        }
        if (formats->protectionInformationSupported.protectionType3Supported)
        {
            print_str("\tType 3 - Logical Block Guard\n");
        }

        if (!formats->protectionInformationSupported.protectionReportedPerSectorSize)
        {
            print_str("      Not all forms of PI are supported on all sector sizes unless otherwise indicated\n");
            print_str("      in the device product manual.\n");
        }
    }
    if (formats->scsiFastFormatSupported)
    {
        print_str("NOTE: This device supports Fast Format. Fast format is not instantaneous and is used for\n");
        print_str(
            "      switching between 5xx and 4xxx sector sizes. A fast format may take a few minutes or longer\n");
        print_str(
            "      but may take longer depending on the size of the drive. Fast format support does not necessarily\n");
        print_str(
            "      mean switching sector sizes AND changing PI at the same time is supported. In most cases, a\n");
        print_str("      switch of PI type will require a full device format.\n");
        print_str(
            "      Fast format mode 1 is typically used to switch from 512 to 4096 block sizes with the current\n");
        print_str("          PI scheme.\n");
    }
    // TODO: NVMe Metadata and PI location information.
}

// this function takes a sector size and maps it to the descriptor check code to use in the set sector configuration
// command
eReturnValues ata_Map_Sector_Size_To_Descriptor_Check(const tDevice* device,
                                                      uint32_t       logicalBlockLength,
                                                      uint16_t*      descriptorCheckCode,
                                                      uint8_t*       descriptorIndex)
{
    eReturnValues ret = SUCCESS;

    if (descriptorCheckCode == M_NULLPTR || descriptorIndex == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }
    else
    {
        *descriptorCheckCode = UINT16_C(0);
    }

    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        uint32_t            formatsDataSize = C_CAST(uint32_t, sizeof(supportedFormats));
        ptrSupportedFormats formats         = M_REINTERPRET_CAST(ptrSupportedFormats, safe_malloc(formatsDataSize));
        if (formats == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        safe_memset(formats, formatsDataSize, 0, formatsDataSize);
        ret = get_Supported_Formats(device, formats);
        if (SUCCESS == ret)
        {
            for (uint32_t sectorSizeIter = UINT32_C(0);
                 sectorSizeIter < formats->numberOfSectorSizes && sectorSizeIter < MAX_SECTOR_SIZES_ARRAY;
                 ++sectorSizeIter)
            {
                if (!formats->sectorSizes[sectorSizeIter].valid)
                {
                    break;
                }
                if (formats->sectorSizes[sectorSizeIter].logicalBlockLength == logicalBlockLength)
                {
                    *descriptorCheckCode = formats->sectorSizes[sectorSizeIter].ataSetSectorFields.descriptorCheck;
                    *descriptorIndex     = formats->sectorSizes[sectorSizeIter].ataSetSectorFields.descriptorIndex;
                    break;
                }
            }
            if (*descriptorCheckCode == 0)
            {
                ret = NOT_SUPPORTED;
            }
        }
    }
    return ret;
}

// this is used to determine which fast format mode to use.
static bool is_Requested_Sector_Size_Multiple(const tDevice* device, uint32_t sectorSize)
{
    uint32_t larger = device->drive_info.deviceBlockSize > sectorSize ? device->drive_info.deviceBlockSize : sectorSize;
    uint32_t smaller =
        device->drive_info.deviceBlockSize < sectorSize ? device->drive_info.deviceBlockSize : sectorSize;
    if (larger == 0 || smaller == 0)
    {
        return false;
    }
    // if there is a remainder in this division then this is not a multiple of
    if (larger % smaller)
    {
        return false;
    }
    else
    {
        return true;
    }
}

eReturnValues set_Sector_Configuration(const tDevice* device, uint32_t sectorSize)
{
    return set_Sector_Configuration_With_Force(device, sectorSize, false);
}

static bool is_ATA_Zero_Ext_Supported_For_MBR_Erase(const tDevice* device)
{
    DECLARE_ZERO_INIT_ARRAY(uint8_t, idDataLogSupportedCapabilities, LEGACY_DRIVE_SEC_SIZE);
    if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA,
                                             ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES, idDataLogSupportedCapabilities,
                                             LEGACY_DRIVE_SEC_SIZE, 0))
    {
        uint64_t qword0 = M_BytesTo8ByteValue(idDataLogSupportedCapabilities[7], idDataLogSupportedCapabilities[6],
                                              idDataLogSupportedCapabilities[5], idDataLogSupportedCapabilities[4],
                                              idDataLogSupportedCapabilities[3], idDataLogSupportedCapabilities[2],
                                              idDataLogSupportedCapabilities[1], idDataLogSupportedCapabilities[0]);
        if (qword0 & BIT63 && M_Byte2(qword0) == ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES && M_Word0(qword0) >= 0x0001)
        {
            uint64_t supportedCapabilitiesQWord =
                M_BytesTo8ByteValue(idDataLogSupportedCapabilities[15], idDataLogSupportedCapabilities[14],
                                    idDataLogSupportedCapabilities[13], idDataLogSupportedCapabilities[12],
                                    idDataLogSupportedCapabilities[11], idDataLogSupportedCapabilities[10],
                                    idDataLogSupportedCapabilities[9], idDataLogSupportedCapabilities[8]);
            if (supportedCapabilitiesQWord & BIT63 && supportedCapabilitiesQWord & BIT48)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    return false;
}

enum
{
    MAX_LBA_RANGE_FOR_MBR_ERASE             = 1,
    MAX_LBA_RANGE_FOR_MBR_ERASE_WITH_BRIDGE = 2
};

// Specific handling for ATA drives. Preference is to use something like write-same or zeroes ext to bypass any
// out-of-sync block size issues with sending any write commands.
// The benefit to these other commands is that they let the drive write based on it's block size rather than
// the host trying to match the block size exactly with passthrough. A passthrough write SHOULD still work, but
// this will bypass any weird issues in case the OS screens the size in some way or fails to complete the command
// correctly
static eReturnValues ata_Passthrough_Erase_MBR(const tDevice* device)
{
    eReturnValues ret            = SUCCESS;
    uint32_t      eraseBlockSize = device->drive_info.deviceBlockSize;
    uint64_t      devMaxLBA      = device->drive_info.deviceMaxLba;
    uint32_t      maxLBARange    = MAX_LBA_RANGE_FOR_MBR_ERASE;
    if (device->drive_info.bridge_info.isValid)
    {
        if (device->drive_info.bridge_info.childDeviceMaxLba > devMaxLBA)
        {
            devMaxLBA   = device->drive_info.bridge_info.childDeviceMaxLba - 1;
            maxLBARange = MAX_LBA_RANGE_FOR_MBR_ERASE_WITH_BRIDGE;
        }
        eraseBlockSize = device->drive_info.bridge_info.childDeviceBlockSize;
    }
    if (is_ATA_Zero_Ext_Supported_For_MBR_Erase(device))
    {
        // use the zero ext command to erase the MBR
        ret = ata_Zeros_Ext(device, maxLBARange, 0, false);
        if (ret == SUCCESS)
        {
            ret = ata_Zeros_Ext(device, maxLBARange, devMaxLBA, false);
            if (ret == SUCCESS)
            {
                return SUCCESS;
            }
        }
    }
    if (is_Write_Same_Supported(device, 0, maxLBARange, M_NULLPTR))
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, zeroPattern, 4);
        ret = send_ATA_SCT_Write_Same(device, WRITE_SAME_FOREGROUND_USE_PATTERN_FIELD, 0, maxLBARange, zeroPattern,
                                      SIZE_OF_STACK_ARRAY(zeroPattern));
        if (ret == SUCCESS)
        {
            ret = send_ATA_SCT_Write_Same(device, WRITE_SAME_FOREGROUND_USE_PATTERN_FIELD, devMaxLBA, maxLBARange,
                                          zeroPattern, SIZE_OF_STACK_ARRAY(zeroPattern));
            if (ret == SUCCESS)
            {
                return SUCCESS;
            }
        }
    }
    // fallback to passthrough write
    uint8_t* eraseMBR = M_REINTERPRET_CAST(
        uint8_t*, safe_calloc_aligned(eraseBlockSize * maxLBARange, sizeof(uint8_t), device->os_info.minimumAlignment));
    ret = ata_Write(device, 0, false, eraseMBR, eraseBlockSize * maxLBARange);
    if (ret == SUCCESS)
    {
        ret = ata_Write(device, devMaxLBA, false, eraseMBR, eraseBlockSize * maxLBARange);
    }
    safe_free_aligned(&eraseMBR);
    return ret;
}

static eReturnValues nvme_Passthrough_Erase_MBR(const tDevice* device)
{
    eReturnValues ret            = SUCCESS;
    uint32_t      eraseBlockSize = device->drive_info.deviceBlockSize;
    uint64_t      devMaxLBA      = device->drive_info.deviceMaxLba;
    uint32_t      maxLBARange    = MAX_LBA_RANGE_FOR_MBR_ERASE;
    if (device->drive_info.bridge_info.isValid)
    {
        if (device->drive_info.bridge_info.childDeviceMaxLba > devMaxLBA)
        {
            devMaxLBA   = device->drive_info.bridge_info.childDeviceMaxLba - 1;
            maxLBARange = MAX_LBA_RANGE_FOR_MBR_ERASE_WITH_BRIDGE;
        }
        eraseBlockSize = device->drive_info.bridge_info.childDeviceBlockSize;
    }
    if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.oncs) & BIT3)
    {
        // Should we set FUA?
        ret = nvme_Write_Zeroes(device, 0, maxLBARange, false, false, false);
        if (ret == SUCCESS)
        {
            ret = nvme_Write_Zeroes(device, devMaxLBA, maxLBARange, false, false, false);
            if (ret == SUCCESS)
            {
                return ret;
            }
        }
    }
    uint8_t* eraseMBR = M_REINTERPRET_CAST(
        uint8_t*, safe_calloc_aligned(eraseBlockSize * maxLBARange, sizeof(uint8_t), device->os_info.minimumAlignment));
    ret = nvme_Write(device, 0, maxLBARange, false, false, 0, 0, eraseMBR, eraseBlockSize * maxLBARange);
    if (ret == SUCCESS)
    {
        ret = nvme_Write(device, devMaxLBA, maxLBARange, false, false, 0, 0, eraseMBR, eraseBlockSize * maxLBARange);
    }
    safe_free_aligned(&eraseMBR);
    return ret;
}

// SCSI Method may get ugly as there may be trial and error.
// Will attempt similar methods to ATA with write same and the equivalent to zeroes ext.
// If those fail, will have to do passthrough write.
static eReturnValues scsi_Passthrough_Erase_MBR(const tDevice* device)
{
    eReturnValues ret                                     = NOT_SUPPORTED;
    eDriveType    temp                                    = device->drive_info.drive_type;
    M_CONST_CAST(tDevice*, device)->drive_info.drive_type = SCSI_DRIVE; // force SCSI drive type for these calls
    ret                                                   = write_Same(device, 0, 1, M_NULLPTR);
    if (ret == SUCCESS)
    {
        ret = write_Same(device, device->drive_info.deviceMaxLba, 1, M_NULLPTR);
        if (ret == SUCCESS)
        {
            M_CONST_CAST(tDevice*, device)->drive_info.drive_type = temp; // restore original drive type
            return SUCCESS;
        }
    }
    // fallback to passthrough write
    uint8_t* eraseMBR =
        M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(device->drive_info.deviceBlockSize, sizeof(uint8_t),
                                                         device->os_info.minimumAlignment));
    ret = scsi_Write(device, 0, false, eraseMBR, device->drive_info.deviceBlockSize);
    if (ret == SUCCESS)
    {
        ret = scsi_Write(device, device->drive_info.deviceMaxLba, false, eraseMBR, device->drive_info.deviceBlockSize);
    }
    safe_free_aligned(&eraseMBR);
    M_CONST_CAST(tDevice*, device)->drive_info.drive_type = temp; // restore original drive type
    return ret;
}

// a weird case was found when changing the sector size on a drive with an existing partition on it.
// Since the MBR was a "dummy" for GPT, it is setup to look like the entire disk has a partition to stop an
// old OS from overwriting partitions setup with GPT.
// So Windows blocks the ability to change the partition.
// The solution is simple: erase the MBR before the format.
// This option already requires a confirmation of data deletion to run, so this should be safe enough. -TJE
static eReturnValues passthrough_Erase_MBR(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.deviceBlockSize > 0)
    {
        if (device->drive_info.drive_type == ATA_DRIVE)
        {
            ret = ata_Passthrough_Erase_MBR(device);
        }
        else if (device->drive_info.drive_type == NVME_DRIVE)
        {
            ret = nvme_Passthrough_Erase_MBR(device);
        }
        if (ret != SUCCESS)
        {
            // This is a final fallback for ATA and NVMe to retry with the SCSI method
            // SCSI will always fall back here as well
            ret = scsi_Passthrough_Erase_MBR(device);
        }
    }
    return ret;
}

eReturnValues set_Sector_Configuration_With_Force(const tDevice* device, uint32_t sectorSize, bool force)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (is_Set_Sector_Configuration_Supported(device) || force)
    {
        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
        {
            print_str("Setting the drive sector size quickly.\n");
            print_str("This command may appear to hang the utility. Do NOT interrupt this\n");
            print_str("command for at least 1 hour if it appears hung. The drive is busy\n");
            print_str("performing the sector size change and is not able to indicate its\n");
            print_str("progress during this time.\n");
            print_str("This often only takes a few minutes to complete and return completion\n");
            print_str("of this command to the utility but in some cases it may take this full\n");
            print_str("hour before the drive is ready to use.\n");
            print_str("After this command has completed successfully the drive is ready to\n");
            print_str("read and write data.\n");
            print_str("Be aware that the drive may begin performing vendor unique background\n");
            print_str("activity which may prevent idle and standby timers from taking affect\n");
            print_str("until the background activity has completed.\n");
            print_str("If this command takes an hour or the command reports a failure due to\n");
            print_str("interruption by the system with a reset, recovery will be attempted\n");
            print_str("automatically. You may attempt to run this command again if recovery\n");
            print_str("does not appear successful.\n");
        }
        os_Get_Exclusive(M_CONST_CAST(tDevice*, device));
        os_Lock_Device(device);
        os_Unmount_File_Systems_On_Device(device);
        if (device->drive_info.drive_type == ATA_DRIVE)
        {
            uint16_t descriptorCheck = UINT16_C(0);
            uint8_t  descriptorIndex = UINT8_C(0);
            ret = ata_Map_Sector_Size_To_Descriptor_Check(device, sectorSize, &descriptorCheck, &descriptorIndex);
            if (SUCCESS == ret)
            {
                ret = ata_Set_Sector_Configuration_Ext(device, descriptorCheck, descriptorIndex);
            }
            delay_Seconds(1);
            // need to call the fill_drive_info again to update device information
            fill_Drive_Info_Data(M_CONST_CAST(tDevice*, device));
            bool setSizeSupported = is_Set_Sector_Configuration_Supported(device);
            if (!setSizeSupported || force)
            {
                if (device->deviceVerbosity >= VERBOSITY_DEFAULT && !setSizeSupported)
                {
                    print_str("ERROR: The device was reset during sector size change. Device may not be usable!\n");
                }
                if (is_Seagate_Family(device) == SEAGATE && !is_SSD(device)) // HDDs only
                {
                    if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                    {
                        print_str("Attempting Seagate quick format to recover the device.\n");
                    }
                    if (SUCCESS != seagate_Quick_Format(device))
                    {
                        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                        {
                            print_str("WARNING: Seagate quick format did not complete successfully!\n");
                        }
                    }
                    // try refreshing the device one more time incase the status was just not right.
                    fill_Drive_Info_Data(M_CONST_CAST(tDevice*, device));
                    if (!is_Set_Sector_Configuration_Supported(device))
                    {
                        // nothing else we can do at this point.
                        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                        {
                            print_str(
                                "ERROR: Quick format did not recover the device. The device may not be usable!\n");
                        }
                    }
                    else
                    {
                        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                        {
                            print_str("Seagate quick format successfully recovered the device!\n");
                            print_str(
                                "If sector size change is attempted again, format only single disks at a time,\n");
                            print_str(
                                "disable all background software, disable any management hardware or software, and "
                                "then\n");
                            print_str("try again if the sector size is not correct.\n");
                        }
                    }
                }
            }
        }
        else // Assume SCSI
        {
            runFormatUnitParameters formatUnitParameters;
            safe_memset(&formatUnitParameters, sizeof(runFormatUnitParameters), 0, sizeof(runFormatUnitParameters));
            formatUnitParameters.formatType           = FORMAT_FAST_WRITE_NOT_REQUIRED;
            formatUnitParameters.currentBlockSize     = false;
            formatUnitParameters.newBlockSize         = C_CAST(uint16_t, sectorSize);
            formatUnitParameters.gList                = M_NULLPTR;
            formatUnitParameters.glistSize            = 0;
            formatUnitParameters.completeList         = false;
            formatUnitParameters.disablePrimaryList   = false;
            formatUnitParameters.disableCertification = false;
            formatUnitParameters.pattern              = M_NULLPTR;
            formatUnitParameters.patternLength        = 0;
            formatUnitParameters.securityInitialize   = false;
            formatUnitParameters.defaultFormat =
                true; // Don't need any option bits! In fact, this could cause an error if not set!
            formatUnitParameters.protectionType             = device->drive_info.currentProtectionType;
            formatUnitParameters.protectionIntervalExponent = device->drive_info.piExponent;
            formatUnitParameters.disableImmediate =
                false; // this will require polling for progress until complete with this disabled, but reduces the
                       // likelyhood of a reset going to the drive.
            // make this smarter to know which type of fast format to use! FAST_FORMAT_WRITE_NOT_REQUIRED is a power of
            // 2 change (512 to 4096), FAST_FORMAT_WRITE_REQUIRED is any other size change
            if (!is_Requested_Sector_Size_Multiple(device, sectorSize))
            {
                formatUnitParameters.formatType = FORMAT_FAST_WRITE_REQUIRED;
            }
            ret = run_Format_Unit(device, formatUnitParameters, true);
        }
        if (ret == SUCCESS)
        {
            fill_Drive_Info_Data(M_CONST_CAST(tDevice*, device));
            // attempt to erase MBR to allow partitioning on drives that had a dummy MBR prior to changing sector size
            if (SUCCESS != passthrough_Erase_MBR(device))
            {
                if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                {
                    print_str("WARNING: Unable to erase MBR after sector size change. You may need to manually "
                              "erase the MBR to repartition the drive.\n");
                }
            }
        }
        os_Unlock_Device(device);
    }
    return ret;
}

eReturnValues get_NVM_Format_Progress(const tDevice* device, uint8_t* percentComplete)
{
    eReturnValues ret = SUCCESS;

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

    *percentComplete = UINT8_C(0);
    if (device->drive_info.drive_type == NVME_DRIVE)
    {
        ret = nvme_Identify(device, M_CONST_CAST(uint8_t*, &device->drive_info.IdentifyData.nvme.ns),
                            device->drive_info.namespaceID, NVME_IDENTIFY_NS);
        if (ret == SUCCESS)
        {
            if (device->drive_info.IdentifyData.nvme.ns.fpi & BIT7)
            {
                if (get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.fpi, 6, 0) != 0)
                {
                    *percentComplete =
                        UINT8_C(100) - get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.fpi, 6, 0);
                    ret = IN_PROGRESS;
                }
            }
        }
    }
    else
    {
        ret = NOT_SUPPORTED;
    }
    return ret;
}

eReturnValues show_NVM_Format_Progress(const tDevice* device)
{
    eReturnValues ret             = UNKNOWN;
    uint8_t       percentComplete = UINT8_C(0);

    ret = get_NVM_Format_Progress(device, &percentComplete);

    if (ret == IN_PROGRESS)
    {
        printf("\tFormat Progress = %" PRIu8 "%% \n", percentComplete);
    }
    else if (ret == SUCCESS)
    {
        print_str("\tA format is not detected as running. Either it is complete or the device does not report its "
                  "progress\n");
    }
    else
    {
        print_str("\tError occurred while retrieving format progress!\n");
    }
    return ret;
}
#define NVME_2_0_MAX_FORMATS 64U
static uint8_t map_NVM_Format_To_Format_Number(const tDevice* device, uint32_t lbaSize, uint16_t metadataSize)
{
    uint8_t fmtNum             = UINT8_MAX;
    uint8_t maxDriveLBAformats = NVME_0_BASED(device->drive_info.IdentifyData.nvme.ns.nlbaf);
    for (uint8_t fmtIter = UINT8_C(0); fmtIter < maxDriveLBAformats && fmtIter < NVME_2_0_MAX_FORMATS; ++fmtIter)
    {
        if (lbaSize == power_Of_Two(device->drive_info.IdentifyData.nvme.ns.lbaf[fmtIter].lbaDS))
        {
            // lba size matches, now check the metadata!
            if (metadataSize == le16_to_host(device->drive_info.IdentifyData.nvme.ns.lbaf[fmtIter].ms))
            {
                fmtNum = fmtIter;
                break;
            }
        }
    }
    return fmtNum;
}

eReturnValues get_NVMe_Format_Support(const tDevice* device, ptrNvmeFormatSupport formatSupport)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (device->drive_info.drive_type == NVME_DRIVE && formatSupport != M_NULLPTR)
    {
        ret = SUCCESS;
        // check FNA field for support
        // bit 0 = format applies to all namespaces. If zero, applies only to the specified namespace
        // bit 1 = secure erase applies to all namespaces. if zero, applies only to the specified namespace
        // bit 2 = cryptographic erase is supported
        if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.oacs) & BIT1)
        {
            formatSupport->formatCommandSupported = true;
            if (device->drive_info.IdentifyData.nvme.ctrl.fna & BIT0)
            {
                formatSupport->formatAppliesToAllNamespaces = true;
            }
            if (device->drive_info.IdentifyData.nvme.ctrl.fna & BIT1)
            {
                formatSupport->secureEraseAppliesToAllNamespaces = true;
            }
            if (device->drive_info.IdentifyData.nvme.ctrl.fna & BIT2)
            {
                formatSupport->cryptographicEraseSupported = true;
            }
            if (device->drive_info.IdentifyData.nvme.ctrl.fna & BIT3) // new in 2.0
            {
                formatSupport->formatNSIDAllNSSupport = false;
            }
            else
            {
                formatSupport->formatNSIDAllNSSupport = true;
            }
        }
    }

    return ret;
}

eReturnValues run_NVMe_Format(const tDevice* device, runNVMFormatParameters nvmParams, bool pollForProgress)
{
    eReturnValues     ret = SUCCESS;
    nvmeFormatCmdOpts formatCmdOptions;
    safe_memset(&formatCmdOptions, sizeof(nvmeFormatCmdOpts), 0, sizeof(nvmeFormatCmdOpts));
    // Set metadata, PI, PIL settings to current device settings to start
    formatCmdOptions.ms  = (device->drive_info.IdentifyData.nvme.ns.mc & BIT0) ? UINT8_C(1) : UINT8_C(0);
    formatCmdOptions.pil = (device->drive_info.IdentifyData.nvme.ns.dps & BIT3) ? UINT8_C(1) : UINT8_C(0);
    formatCmdOptions.pi  = get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.dps, 2, 0);

    if (nvmParams.metadataSettings.valid)
    {
        formatCmdOptions.ms = nvmParams.metadataSettings.metadataAsExtendedLBA ? UINT8_C(1) : UINT8_C(0);
    }

    if (nvmParams.protectionLocation.valid)
    {
        formatCmdOptions.pil = nvmParams.protectionLocation.first8Bytes ? UINT8_C(1) : UINT8_C(0);
    }

    if (nvmParams.changeProtectionType)
    {
        formatCmdOptions.pi = nvmParams.protectionType;
    }

    if (nvmParams.formatNumberProvided)
    {
        formatCmdOptions.lbaf = nvmParams.formatNumber;
    }
    else
    {
        // need to figure out what format we want to run!
        uint8_t flbas = get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.flbas, 3, 0);
        // get the LBAF number. THis field varies depending on other things reported by the drive in NVMe 2.0
        if (NVME_0_BASED(device->drive_info.IdentifyData.nvme.ns.nlbaf) > 16)
        {
            // need to append 2 more bits to interpret this correctly since number of formats > 16
            flbas |= get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ns.flbas, 6, 5) << 4;
        }
        // cast on blocksize is ok because it will not ever be a value greater than UINT32_MAX
        uint32_t fmtBlockSize =
            M_STATIC_CAST(uint32_t, power_Of_Two(device->drive_info.IdentifyData.nvme.ns.lbaf[flbas].lbaDS));
        uint16_t fmtMetaDataSize = le16_to_host(device->drive_info.IdentifyData.nvme.ns.lbaf[flbas].ms);

        if (!nvmParams.newSize.currentBlockSize)
        {
            fmtBlockSize = nvmParams.newSize.newBlockSize;
        }

        if (!nvmParams.newSize.changeMetadataSize)
        {
            fmtMetaDataSize = nvmParams.newSize.metadataSize;
        }

        formatCmdOptions.lbaf = map_NVM_Format_To_Format_Number(device, fmtBlockSize, fmtMetaDataSize);
    }
    // invalid format requested.
    if (formatCmdOptions.lbaf > NVME_0_BASED(device->drive_info.IdentifyData.nvme.ns.nlbaf))
    {
        if (device->deviceVerbosity > VERBOSITY_QUIET)
        {
            print_str("ERROR: Invalid format requested\n");
        }
        return NOT_SUPPORTED;
    }

    if (nvmParams.secureEraseSettings == NVM_FMT_SE_CRYPTO && (!(device->drive_info.IdentifyData.nvme.ctrl.fna & BIT2)))
    {
        if (device->deviceVerbosity > VERBOSITY_QUIET)
        {
            print_str("ERROR: Crypto Erase not supported by the device\n");
        }
        return NOT_SUPPORTED;
    }

    switch (nvmParams.secureEraseSettings)
    {
    case NVM_FMT_SE_USER_DATA:
        formatCmdOptions.ses = FORMAT_NVME_ERASE_USER_DATA;
        break;
    case NVM_FMT_SE_CRYPTO:
        formatCmdOptions.ses = FORMAT_NVME_CRYPTO_ERASE;
        break;
    case NVM_FMT_SE_NO_SECURE_ERASE_REQUESTED:
    default:
        formatCmdOptions.ses = FORMAT_NVME_NO_SECURE_ERASE;
        break;
    }
    if (nvmParams.currentNamespace)
    {
        formatCmdOptions.nsid = device->drive_info.namespaceID;
    }
    else
    {
        formatCmdOptions.nsid = NVME_ALL_NAMESPACES;
    }
    ret = nvme_Format(device, &formatCmdOptions);
    if (pollForProgress && ret == SUCCESS)
    {
        uint32_t delayTimeSeconds = UINT32_C(5);
        uint8_t  progress         = UINT8_C(0);
        // 2 second delay to make sure it starts (and on SSD this may be enough for it to finish immediately in some
        // cases)
        delay_Seconds(2);
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            uint8_t seconds = UINT8_C(0);
            uint8_t minutes = UINT8_C(0);
            uint8_t hours   = UINT8_C(0);
            convert_Seconds_To_Displayable_Time(delayTimeSeconds, M_NULLPTR, M_NULLPTR, &hours, &minutes, &seconds);
            print_str("Progress will be updated every ");
            print_Time_To_Screen(M_NULLPTR, M_NULLPTR, &hours, &minutes, &seconds);
            print_str("\n");
        }
        while (IN_PROGRESS == (ret = get_NVM_Format_Progress(device, &progress)) && progress < 100.0)
        {
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                printf("\r\tPercent Complete: %" PRIu8 "%%", progress);
                flush_stdout();
            }
            delay_Seconds(delayTimeSeconds); // time set above
        }
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            print_str("\n");
        }
        if (nvmParams.newSize.currentBlockSize)
        {
            os_Update_File_System_Cache(device);
        }
    }
    return ret;
}
