// SPDX-License-Identifier: MPL-2.0
//
// Do NOT modify or remove this copyright and license
//
// Copyright (c) 2021-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 reservations.c   SCSI reservations based functions. NOTE: THis is SCSI-3 persistent reservations only at this
// time - TJE

// NOTE: Old reservations from SCSI2 (and up to SPC2) are not currently supported. These are much more limited than what
// is available in the Persistent Reservations feature NOTE: Some additional enhancement for NVMe is probably possible.
// The current NVMe implementation is more-or-less what is part of NVMe to SCSI translation. - TJE

#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 "string_utils.h"
#include "type_conversion.h"

#include "reservations.h"
#include "scsi_helper.h"
#include "scsi_helper_func.h"

bool is_Persistent_Reservations_Supported(const tDevice* device)
{
    bool supported = false;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        // try a persistent reserve in with no data transferred. This SHOULD not return an error if this feature is
        // supported - TJE
        if (SUCCESS == scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_KEYS, 0, M_NULLPTR))
        {
            supported = true;
        }
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        // Controller identify says if the commands are supported
        // NS identify says which types of reservations are supported...should have both non-zero if supported - TJE
        if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.oncs) & BIT5 &&
            device->drive_info.IdentifyData.nvme.ns.rescap > 0)
        {
            supported = true;
        }
    }
    return supported;
}

typedef struct s_reservationTypesSupportedV1
{
    bool readShared; // marked as reserved in spec...but pretty sure it was reserved for this
    bool writeExclusive;
    bool readExclusive; // marked as reserved in spec...but pretty sure it was reserved for this
    bool exclusiveAccess;
    bool sharedAccess; // marked as reserved in spec...but pretty sure it was reserved for this
    bool writeExclusiveRegistrantsOnly;
    bool exclusiveAccessRegistrantsOnly;
    bool writeExclusiveAllRegistrants;
    bool exclusiveAccessAllRegistrants;
    // remaining are reserved, but making each a bool indicating SCSI mode number in case it expands in the future
    bool reserved9h;
    bool reservedAh;
    bool reservedBh;
    bool reservedCh;
    bool reservedDh;
    bool reservedEh;
    bool reservedFh;
} reservationTypesSupportedV1;

typedef enum eAllowedCommandDetailV1Enum
{
    RES_CMD_ALLOWED_NO_INFO_V1,
    RES_CMD_ALLOWED_WE_AND_EA_V1,
    RES_CMD_NOT_ALLOWED_WE_V1,
    RES_CMD_ALLOWED_WE_V1,
    RES_CMD_PERSIST_ALLOWED_WE_AND_EA_V1,
    RES_CMD_PERSIST_ALLOWED_WE_V1,
} eAllowedCommandDetailV1;

typedef struct s_allowedCommandsV1
{
    uint8_t allowedCommandsRawValue; // 0 - 7 as reported by the drive in case the remaining info is not useful enough
    eAllowedCommandDetailV1 testUnitReady;
    eAllowedCommandDetailV1 modeSense;
    eAllowedCommandDetailV1 readAttribute;
    eAllowedCommandDetailV1 readBuffer10;
    eAllowedCommandDetailV1 receiveDiagnosticResults;
    eAllowedCommandDetailV1 reportSupportedOperationCodes;
    eAllowedCommandDetailV1 reportSupportedTaskManagementFunctions;
    eAllowedCommandDetailV1 readDefectData;
} allowedCommandsV1;

typedef struct s_persistentReservationCapabilitiesV1
{
    size_t            size;
    uint32_t          version;
    bool              replaceLostReservationCapable;
    bool              compatibleReservationHandling; // scsi2 reservation reelease/reserve exceptions
    bool              specifyInitiatorPortCapable;
    bool              allTargetPortsCapable;
    bool              persistThroughPowerLossCapable;
    allowedCommandsV1 allowedCommandsInfo;
    bool              persistThroughPowerLossActivated;
    bool reservationTypesSupportedValid; // If set to true, the device reported the type mask indicating which
                                         // reservation types are supported (below)
    reservationTypesSupported reservationsCapabilities;
} persistentReservationCapabilitiesV1, *ptrPersistentReservationCapabilitiesV1;

#define PERSISTENT_RESERVATION_CAPABILITIES_VERSION_V1 1

eReturnValues get_Persistent_Reservations_Capabilities(const tDevice*                       device,
                                                       ptrPersistentReservationCapabilities prCapabilities)
{
    // note: some older drives don't support report capabilities...need to figure out what to do about those - TJE
    eReturnValues ret = NOT_SUPPORTED;

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

    if (!(prCapabilities->version >= PERSISTENT_RESERVATION_CAPABILITIES_VERSION_V1 &&
          prCapabilities->size >= sizeof(persistentReservationCapabilitiesV1)))
    {
        return BAD_PARAMETER;
    }
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, capabilities, 8);
        if (SUCCESS ==
            (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_REPORT_CAPABILITIES, 8, capabilities)))
        {
            uint16_t prCapabilitiesLength = M_BytesTo2ByteValue(capabilities[0], capabilities[1]);
            if (prCapabilitiesLength >= 8)
            {
                if (capabilities[2] & BIT7)
                {
                    prCapabilities->replaceLostReservationCapable = true;
                }
                else
                {
                    prCapabilities->replaceLostReservationCapable = false;
                }
                if (capabilities[2] & BIT4)
                {
                    prCapabilities->compatibleReservationHandling = true;
                }
                else
                {
                    prCapabilities->compatibleReservationHandling = false;
                }
                if (capabilities[2] & BIT3)
                {
                    prCapabilities->specifyInitiatorPortCapable = true;
                }
                else
                {
                    prCapabilities->specifyInitiatorPortCapable = false;
                }
                if (capabilities[2] & BIT2)
                {
                    prCapabilities->allTargetPortsCapable = true;
                }
                else
                {
                    prCapabilities->allTargetPortsCapable = false;
                }
                if (capabilities[2] & BIT0)
                {
                    prCapabilities->persistThroughPowerLossCapable = true;
                }
                else
                {
                    prCapabilities->persistThroughPowerLossCapable = false;
                }
                prCapabilities->allowedCommandsInfo.allowedCommandsRawValue =
                    get_bit_range_uint8(capabilities[3], 6, 4);
                switch (prCapabilities->allowedCommandsInfo.allowedCommandsRawValue)
                {
                case 0:
                    // no info for any
                    prCapabilities->allowedCommandsInfo.testUnitReady                 = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.modeSense                     = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.readAttribute                 = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.readBuffer10                  = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults      = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions =
                        RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.readDefectData = RES_CMD_ALLOWED_NO_INFO;
                    break;
                case 1:
                    // test unit ready, no further info
                    prCapabilities->allowedCommandsInfo.testUnitReady                 = RES_CMD_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.modeSense                     = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.readAttribute                 = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.readBuffer10                  = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults      = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes = RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions =
                        RES_CMD_ALLOWED_NO_INFO;
                    prCapabilities->allowedCommandsInfo.readDefectData = RES_CMD_ALLOWED_NO_INFO;
                    break;
                case 2:
                    // test unit ready supported, all others not allowed.
                    prCapabilities->allowedCommandsInfo.testUnitReady                 = RES_CMD_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.modeSense                     = RES_CMD_NOT_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readAttribute                 = RES_CMD_NOT_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readBuffer10                  = RES_CMD_NOT_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults      = RES_CMD_NOT_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes = RES_CMD_NOT_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions = RES_CMD_NOT_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readDefectData                         = RES_CMD_NOT_ALLOWED_WE;
                    break;
                case 3:
                    // test unit ready supported, all others allowed in WE.
                    prCapabilities->allowedCommandsInfo.testUnitReady                 = RES_CMD_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.modeSense                     = RES_CMD_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readAttribute                 = RES_CMD_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readBuffer10                  = RES_CMD_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults      = RES_CMD_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes = RES_CMD_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions = RES_CMD_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readDefectData                         = RES_CMD_ALLOWED_WE;
                    break;
                case 4:
                    // same as 3, but persistent
                    prCapabilities->allowedCommandsInfo.testUnitReady            = RES_CMD_PERSIST_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.modeSense                = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readAttribute            = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readBuffer10             = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions =
                        RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readDefectData = RES_CMD_PERSIST_ALLOWED_WE;
                    break;
                case 5:
                    // a few more commands than 4 in WE and EA
                    prCapabilities->allowedCommandsInfo.testUnitReady            = RES_CMD_PERSIST_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.modeSense                = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readAttribute            = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.readBuffer10             = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults = RES_CMD_PERSIST_ALLOWED_WE;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes =
                        RES_CMD_PERSIST_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions =
                        RES_CMD_PERSIST_ALLOWED_WE_AND_EA;
                    prCapabilities->allowedCommandsInfo.readDefectData = RES_CMD_PERSIST_ALLOWED_WE;
                    break;
                default:
                    prCapabilities->allowedCommandsInfo.testUnitReady                          = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.modeSense                              = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.readAttribute                          = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.readBuffer10                           = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.receiveDiagnosticResults               = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes          = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions = RES_CMD_UNKNOWN;
                    prCapabilities->allowedCommandsInfo.readDefectData                         = RES_CMD_UNKNOWN;
                    break;
                }

                if (capabilities[3] & BIT0)
                {
                    prCapabilities->persistThroughPowerLossActivated = true;
                }
                else
                {
                    prCapabilities->persistThroughPowerLossActivated = false;
                }
                if (capabilities[3] & BIT7)
                {
                    prCapabilities->reservationTypesSupportedValid = true;
                    if (capabilities[4] & BIT0)
                    {
                        prCapabilities->reservationsCapabilities.readShared = true;
                    }
                    if (capabilities[4] & BIT1)
                    {
                        prCapabilities->reservationsCapabilities.writeExclusive = true;
                    }
                    if (capabilities[4] & BIT2)
                    {
                        prCapabilities->reservationsCapabilities.readExclusive = true;
                    }
                    if (capabilities[4] & BIT3)
                    {
                        prCapabilities->reservationsCapabilities.exclusiveAccess = true;
                    }
                    if (capabilities[4] & BIT4)
                    {
                        prCapabilities->reservationsCapabilities.sharedAccess = true;
                    }
                    if (capabilities[4] & BIT5)
                    {
                        prCapabilities->reservationsCapabilities.writeExclusiveRegistrantsOnly = true;
                    }
                    if (capabilities[4] & BIT6)
                    {
                        prCapabilities->reservationsCapabilities.exclusiveAccessRegistrantsOnly = true;
                    }
                    if (capabilities[4] & BIT7)
                    {
                        prCapabilities->reservationsCapabilities.writeExclusiveAllRegistrants = true;
                    }
                    if (capabilities[5] & BIT0)
                    {
                        prCapabilities->reservationsCapabilities.exclusiveAccessAllRegistrants = true;
                    }
                    if (capabilities[5] & BIT1)
                    {
                        prCapabilities->reservationsCapabilities.reserved9h = true;
                    }
                    if (capabilities[5] & BIT2)
                    {
                        prCapabilities->reservationsCapabilities.reservedAh = true;
                    }
                    if (capabilities[5] & BIT3)
                    {
                        prCapabilities->reservationsCapabilities.reservedBh = true;
                    }
                    if (capabilities[5] & BIT4)
                    {
                        prCapabilities->reservationsCapabilities.reservedCh = true;
                    }
                    if (capabilities[5] & BIT5)
                    {
                        prCapabilities->reservationsCapabilities.reservedDh = true;
                    }
                    if (capabilities[5] & BIT6)
                    {
                        prCapabilities->reservationsCapabilities.reservedEh = true;
                    }
                    if (capabilities[5] & BIT7)
                    {
                        prCapabilities->reservationsCapabilities.reservedFh = true;
                    }
                }
                else
                {
                    prCapabilities->reservationTypesSupportedValid = false;
                    safe_memset(&prCapabilities->reservationsCapabilities, sizeof(reservationTypesSupported), 0,
                                sizeof(reservationTypesSupported));
                }
            }
            else
            {
                ret = UNKNOWN;
            }
        }
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        nvmeFeaturesCmdOpt getReservatinPersistence;
        safe_memset(&getReservatinPersistence, sizeof(nvmeFeaturesCmdOpt), 0, sizeof(nvmeFeaturesCmdOpt));
        getReservatinPersistence.fid  = NVME_FEAT_RESERVATION_PERSISTANCE_;
        getReservatinPersistence.nsid = device->drive_info.namespaceID;
        prCapabilities->replaceLostReservationCapable =
            true; // not in NVMe translation, but seems to be part of NVMe spec
        prCapabilities->compatibleReservationHandling = false;
        prCapabilities->specifyInitiatorPortCapable   = false;
        prCapabilities->allTargetPortsCapable         = true;
        prCapabilities->persistThroughPowerLossCapable =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT0);
        // need to do get features to figure out if persist through power loss activated is true or false
        prCapabilities->allowedCommandsInfo.allowedCommandsRawValue                = 0;
        prCapabilities->allowedCommandsInfo.testUnitReady                          = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.modeSense                              = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.readAttribute                          = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.readBuffer10                           = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.receiveDiagnosticResults               = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes          = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->allowedCommandsInfo.readDefectData                         = RES_CMD_ALLOWED_NO_INFO;
        prCapabilities->reservationTypesSupportedValid                             = true;
        prCapabilities->reservationsCapabilities.readShared                        = false;
        prCapabilities->reservationsCapabilities.writeExclusive =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT1);
        prCapabilities->reservationsCapabilities.readExclusive = false;
        prCapabilities->reservationsCapabilities.exclusiveAccess =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT2);
        prCapabilities->reservationsCapabilities.sharedAccess = false;
        prCapabilities->reservationsCapabilities.writeExclusiveRegistrantsOnly =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT3);
        prCapabilities->reservationsCapabilities.exclusiveAccessRegistrantsOnly =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT4);
        prCapabilities->reservationsCapabilities.writeExclusiveAllRegistrants =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT5);
        prCapabilities->reservationsCapabilities.exclusiveAccessAllRegistrants =
            M_ToBool(device->drive_info.IdentifyData.nvme.ns.rescap & BIT6);
        prCapabilities->reservationsCapabilities.reserved9h = false;
        prCapabilities->reservationsCapabilities.reservedAh = false;
        prCapabilities->reservationsCapabilities.reservedBh = false;
        prCapabilities->reservationsCapabilities.reservedCh = false;
        prCapabilities->reservationsCapabilities.reservedDh = false;
        prCapabilities->reservationsCapabilities.reservedEh = false;
        prCapabilities->reservationsCapabilities.reservedFh = false;
        if (SUCCESS == (ret = nvme_Get_Features(device, &getReservatinPersistence)))
        {
            // in dw0 completion
            prCapabilities->persistThroughPowerLossActivated =
                M_ToBool(device->drive_info.lastNVMeResult.lastNVMeCommandSpecific & BIT0);
        }
    }
    return ret;
}

static void show_Allowed_Commands_Value(eAllowedCommandDetail value)
{
    switch (value)
    {
    case RES_CMD_ALLOWED_NO_INFO:
        print_str("No Information\n");
        break;
    case RES_CMD_ALLOWED_WE_AND_EA:
        print_str("Allowed in write exclusive and exclusive access\n");
        break;
    case RES_CMD_NOT_ALLOWED_WE:
        print_str("Not allowed in write exclusive\n");
        break;
    case RES_CMD_ALLOWED_WE:
        print_str("Allowed in write exclusive\n");
        break;
    case RES_CMD_PERSIST_ALLOWED_WE_AND_EA:
        print_str("Allowed in persistent write exclusive and persistent exclusive\n");
        break;
    case RES_CMD_PERSIST_ALLOWED_WE:
        print_str("Allowed in persistent write exclusive\n");
        break;
    case RES_CMD_UNKNOWN:
    default:
        print_str("Unknown\n");
        break;
    }
}

void show_Persistent_Reservations_Capabilities(ptrPersistentReservationCapabilities prCapabilities)
{

    if (prCapabilities == M_NULLPTR)
    {
        if ((prCapabilities->version >= PERSISTENT_RESERVATION_CAPABILITIES_VERSION_V1 &&
             prCapabilities->size >= sizeof(persistentReservationCapabilitiesV1)))
        {
            print_str("\nPersistent Reservations Capabilities:\n");
            print_str("=====================================\n");
            print_str("\tReplace Lost Reservations Capable: ");
            if (prCapabilities->replaceLostReservationCapable)
            {
                print_str("supported\n");
            }
            else
            {
                print_str("not supported\n");
            }
            print_str("\tCompatible Reservation Handling: ");
            if (prCapabilities->compatibleReservationHandling)
            {
                print_str("supported\n");
            }
            else
            {
                print_str("not supported\n");
            }
            print_str("\tSpecify Initiator Port Capable: ");
            if (prCapabilities->specifyInitiatorPortCapable)
            {
                print_str("supported\n");
            }
            else
            {
                print_str("not supported\n");
            }
            print_str("\tAll Target Ports Capable: ");
            if (prCapabilities->allTargetPortsCapable)
            {
                print_str("supported\n");
            }
            else
            {
                print_str("not supported\n");
            }
            print_str("\tPersist Through Power Loss Capable: ");
            if (prCapabilities->persistThroughPowerLossCapable)
            {
                print_str("supported\n");
            }
            else
            {
                print_str("not supported\n");
            }
            print_str("\tPersist Through Power Loss Activated: ");
            if (prCapabilities->persistThroughPowerLossActivated)
            {
                print_str("Enabled\n");
            }
            else
            {
                print_str("Disabled\n");
            }
            if (prCapabilities->reservationTypesSupportedValid)
            {
                print_str("\n\tSupported Reservation Types:\n");
                print_str("\t----------------------------\n");
                if (prCapabilities->reservationsCapabilities.readShared)
                {
                    print_str("\t\tRead Shared\n");
                }
                if (prCapabilities->reservationsCapabilities.writeExclusive)
                {
                    print_str("\t\tWrite Exclusive\n");
                }
                if (prCapabilities->reservationsCapabilities.readExclusive)
                {
                    print_str("\t\tRead Exclusive\n");
                }
                if (prCapabilities->reservationsCapabilities.exclusiveAccess)
                {
                    print_str("\t\tExclusive Access\n");
                }
                if (prCapabilities->reservationsCapabilities.sharedAccess)
                {
                    print_str("\t\tShared Access\n");
                }
                if (prCapabilities->reservationsCapabilities.writeExclusiveRegistrantsOnly)
                {
                    print_str("\t\tWrite Exclusive - Registrants Only\n");
                }
                if (prCapabilities->reservationsCapabilities.exclusiveAccessRegistrantsOnly)
                {
                    print_str("\t\tExclusive Access - Registrants Only\n");
                }
                if (prCapabilities->reservationsCapabilities.writeExclusiveAllRegistrants)
                {
                    print_str("\t\tWrite Exclusive - All Registrants\n");
                }
                if (prCapabilities->reservationsCapabilities.exclusiveAccessAllRegistrants)
                {
                    print_str("\t\tExclusive Access - All Registrants\n");
                }
            }
            else
            {
                print_str("\tDevice does not report supported reservation types.\n");
            }
            if (prCapabilities->allowedCommandsInfo.allowedCommandsRawValue <
                6) // restricted like this since this is reserved at the time of writing this code - TJE
            {
                print_str("\n\tAllowed Commands Info:\n");
                print_str("\t----------------------\n");
                print_str("\t\tTest Unit Ready: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.testUnitReady);
                print_str("\t\tMode Sense: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.modeSense);
                print_str("\t\tRead Attribute: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.readAttribute);
                print_str("\t\tRead Buffer (10): ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.readBuffer10);
                print_str("\t\tReceive Diagnostic Results: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.receiveDiagnosticResults);
                print_str("\t\tReport Supported Operation Codes: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.reportSupportedOperationCodes);
                print_str("\t\tReport Supported Task Management Functions: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.reportSupportedTaskManagementFunctions);
                print_str("\t\tRead Defect Data: ");
                show_Allowed_Commands_Value(prCapabilities->allowedCommandsInfo.readDefectData);
            }
            else
            {
                printf("\tAllowed Commands: Not Reportable. Reserved value reported: %" PRIu8 "\n",
                       prCapabilities->allowedCommandsInfo.allowedCommandsRawValue);
            }
        }
        else
        {
            print_str("Error: Incorrect reservations capabilities structure version or bad structure size.\n");
        }
    }
}

eReturnValues get_Registration_Key_Count(const tDevice* device, uint16_t* keyCount)
{
    eReturnValues ret = NOT_SUPPORTED;

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

    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, readKeyCount, 8);
        if (SUCCESS ==
            (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_KEYS, 8, readKeyCount)))
        {
            *keyCount = C_CAST(uint16_t,
                               M_BytesTo4ByteValue(readKeyCount[4], readKeyCount[5], readKeyCount[6], readKeyCount[7]) /
                                   UINT32_C(8)); // each registered key is 8 bytes in length
        }
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(
            uint8_t, readKeyCount,
            24); // may be able to get away with only 8 bytes, but this read up until the list begins - TJE
        if (SUCCESS == (ret = nvme_Reservation_Report(device, false, readKeyCount, 24)))
        {
            *keyCount = M_BytesTo2ByteValue(readKeyCount[6], readKeyCount[5]);
        }
    }
    return ret;
}

#define REGISTRATION_KEY_DATA_VERSION_V1 1

typedef struct s_registrationKeysDataV1
{
    size_t   size;
    uint32_t version;
    uint32_t generation;         // counter that updates each time new registration is added or removed
    uint16_t numberOfKeys;       // number of keys reported below
    uint64_t registrationKey[1]; // This is variable sized depending on how many are requested to be read and how many
                                 // are filled in when read.
} registrationKeysDataV1, *ptrRegistrationKeysDataV1;

eReturnValues get_Registration_Keys(const tDevice* device, uint16_t numberOfKeys, ptrRegistrationKeysData keys)
{
    // get only registration keys
    eReturnValues ret = NOT_SUPPORTED;

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

    if (!(keys->version >= REGISTRATION_KEY_DATA_VERSION_V1) && !(keys->size >= sizeof(registrationKeysDataV1)))
    {
        return BAD_PARAMETER;
    }
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        uint16_t dataLength       = C_CAST(uint16_t, (numberOfKeys * UINT16_C(8)) + UINT16_C(8));
        uint8_t* registrationKeys = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(dataLength, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (registrationKeys == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        if (SUCCESS == (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_KEYS, dataLength,
                                                         registrationKeys)))
        {
            uint16_t reportedKeys = C_CAST(uint16_t, M_BytesTo4ByteValue(registrationKeys[4], registrationKeys[5],
                                                                         registrationKeys[6], registrationKeys[7]) /
                                                         UINT32_C(8)); // each registered key is 8 bytes in length
            keys->generation =
                M_BytesTo4ByteValue(registrationKeys[0], registrationKeys[1], registrationKeys[2], registrationKeys[3]);
            // loop through and save each key
            keys->numberOfKeys = 0;
            for (uint32_t keyIter = UINT32_C(0), offset = UINT32_C(8);
                 keyIter < reportedKeys && keyIter < numberOfKeys && offset < dataLength;
                 ++keys->numberOfKeys, ++keyIter, offset += UINT32_C(8))
            {
                keys->registrationKey[keyIter] = M_BytesTo8ByteValue(
                    registrationKeys[offset + 0], registrationKeys[offset + 1], registrationKeys[offset + 2],
                    registrationKeys[offset + 3], registrationKeys[offset + 4], registrationKeys[offset + 5],
                    registrationKeys[offset + 6], registrationKeys[offset + 7]);
            }
        }
        safe_free_aligned(&registrationKeys);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        uint32_t dataLength =
            (C_CAST(uint32_t, numberOfKeys) * UINT32_C(24)) +
            UINT32_C(24); // 24 byte header, then 24 bytes per key....if extended, then it is even larger.
        uint8_t* registrationKeys = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(dataLength, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (registrationKeys == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        keys->numberOfKeys = UINT16_C(0);
        if (SUCCESS == (ret = nvme_Reservation_Report(device, false, registrationKeys, dataLength)))
        {
            uint16_t reportedKeys = M_BytesTo2ByteValue(registrationKeys[6], registrationKeys[5]);
            keys->generation =
                M_BytesTo4ByteValue(registrationKeys[3], registrationKeys[2], registrationKeys[1], registrationKeys[0]);
            // loop through and save each key
            for (uint32_t keyIter = UINT32_C(0), offset = UINT32_C(24);
                 keyIter < reportedKeys && keyIter < numberOfKeys && offset < dataLength;
                 ++keys->numberOfKeys, ++keyIter, offset += UINT32_C(24))
            {
                keys->registrationKey[keyIter] = M_BytesTo8ByteValue(
                    registrationKeys[offset + 23], registrationKeys[offset + 22], registrationKeys[offset + 21],
                    registrationKeys[offset + 20], registrationKeys[offset + 19], registrationKeys[offset + 18],
                    registrationKeys[offset + 17], registrationKeys[offset + 16]);
            }
        }
        safe_free_aligned(&registrationKeys);
    }
    return ret;
}

void show_Registration_Keys(ptrRegistrationKeysData keys)
{

    if (keys != M_NULLPTR && keys->version >= REGISTRATION_KEY_DATA_VERSION_V1 &&
        keys->size >= sizeof(registrationKeysDataV1))
    {
        print_str("\nRegistration Keys:\n");
        print_str("==================\n");
        printf("Generation: %" PRIu32 "\n", keys->generation);
        print_str("------------------------\n");
        for (uint32_t keyIter = UINT32_C(0); keyIter < UINT16_MAX && keyIter < keys->numberOfKeys; ++keyIter)
        {
            printf("%016" PRIX64 "\n", keys->registrationKey[keyIter]);
        }
        if (keys->numberOfKeys == 0)
        {
            print_str("No registration keys to report.\n");
        }
    }
}

// If supporting "extents", multiple can be reported, but this capability is obsolete, so this will likely return 1 or 0
eReturnValues get_Reservation_Count(const tDevice* device, uint16_t* reservationKeyCount)
{
    // get only reservations
    eReturnValues ret = NOT_SUPPORTED;

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

    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, reservationKeys, 8);
        if (SUCCESS ==
            (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_RESERVATION, 8, reservationKeys)))
        {
            *reservationKeyCount = C_CAST(uint16_t, M_BytesTo4ByteValue(reservationKeys[4], reservationKeys[5],
                                                                        reservationKeys[6], reservationKeys[7]) /
                                                        UINT32_C(16));
        }
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(
            uint8_t, readKeyCount,
            24); // may be able to get away with only 8 bytes, but this read up until the list begins - TJE
        if (SUCCESS == (ret = nvme_Reservation_Report(device, false, readKeyCount, 24)))
        {
            if (readKeyCount[4] > 0)
            {
                *reservationKeyCount = 1;
            }
            else
            {
                *reservationKeyCount = 0;
            }
        }
    }
    return ret;
}

typedef struct s_reservationInfoV1
{
    uint64_t          reservationKey;
    uint32_t          scopeSpecificAddress; // obsolete
    eReservationScope scope;
    eReservationType  type;
    uint16_t          extentLength; // obsolete
} reservationInfoV1;

#define RESERVATION_DATA_VERSION_V1 1

typedef struct s_reservationsDataV1
{
    size_t   size;
    uint32_t version;
    uint32_t generation;
    uint16_t numberOfReservations; // will most likely be 0 or 1 since element and extent types are obsolete.
    reservationInfoV1
        reservation[1]; // variable length depending on how it was allocated. Should always be AT LEAST one of these
} reservationsDataV1, *ptrReservationsDataV1;

eReturnValues get_Reservations(const tDevice* device, uint16_t numberReservations, ptrReservationsData reservations)
{
    // get only reservations
    eReturnValues ret = NOT_SUPPORTED;

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

    if (!(reservations->version >= RESERVATION_DATA_VERSION_V1) && !(reservations->size >= sizeof(reservationsDataV1)))
    {
        return BAD_PARAMETER;
    }
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        uint16_t reservationsLength = C_CAST(uint16_t, numberReservations* UINT16_C(16) + UINT16_C(8));
        uint8_t* reservationKeys    = C_CAST(
            uint8_t*, safe_calloc_aligned(reservationsLength, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (reservationKeys == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        reservations->numberOfReservations = 0;
        if (SUCCESS == (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_RESERVATION,
                                                         reservationsLength, reservationKeys)))
        {
            uint16_t reservationKeyCount =
                C_CAST(uint16_t, M_BytesTo4ByteValue(reservationKeys[4], reservationKeys[5], reservationKeys[6],
                                                     reservationKeys[7]) /
                                     UINT32_C(16));
            reservations->generation =
                M_BytesTo4ByteValue(reservationKeys[0], reservationKeys[1], reservationKeys[2], reservationKeys[3]);
            for (uint32_t reservationIter = UINT32_C(0), offset = UINT32_C(8);
                 reservationIter < reservationKeyCount && reservationIter < numberReservations &&
                 offset < reservationsLength;
                 ++reservations->numberOfReservations, ++reservationIter, offset += UINT32_C(16))
            {
                reservations->reservation[reservationIter].reservationKey = M_BytesTo8ByteValue(
                    reservationKeys[offset + 0], reservationKeys[offset + 1], reservationKeys[offset + 2],
                    reservationKeys[offset + 3], reservationKeys[offset + 4], reservationKeys[offset + 5],
                    reservationKeys[offset + 6], reservationKeys[offset + 7]);
                reservations->reservation[reservationIter].scopeSpecificAddress = M_BytesTo4ByteValue(
                    reservationKeys[offset + 8], reservationKeys[offset + 9], reservationKeys[offset + 10],
                    reservationKeys[offset + 11]); // obsolete on newer drives
                reservations->reservation[reservationIter].extentLength = M_BytesTo2ByteValue(
                    reservationKeys[offset + 14], reservationKeys[offset + 15]); // obsolete on newer drives
                switch (get_bit_range_uint8(reservationKeys[offset + 13], 7, 4))
                {
                case 0:
                    reservations->reservation[reservationIter].scope = RESERVATION_SCOPE_LOGICAL_UNIT;
                    break;
                case 1:
                    reservations->reservation[reservationIter].scope = RESERVATION_SCOPE_EXTENT;
                    break;
                case 2:
                    reservations->reservation[reservationIter].scope = RESERVATION_SCOPE_ELEMENT;
                    break;
                default:
                    reservations->reservation[reservationIter].scope = RESERVATION_SCOPE_UNKNOWN;
                    break;
                }
                switch (get_bit_range_uint8(reservationKeys[offset + 13], 3, 0))
                {
                case 0:
                    reservations->reservation[reservationIter].type = RES_TYPE_READ_SHARED;
                    break;
                case 1:
                    reservations->reservation[reservationIter].type = RES_TYPE_WRITE_EXCLUSIVE;
                    break;
                case 2:
                    reservations->reservation[reservationIter].type = RES_TYPE_READ_EXCLUSIVE;
                    break;
                case 3:
                    reservations->reservation[reservationIter].type = RES_TYPE_EXCLUSIVE_ACCESS;
                    break;
                case 4:
                    reservations->reservation[reservationIter].type = RES_TYPE_SHARED_ACCESS;
                    break;
                case 5:
                    reservations->reservation[reservationIter].type = RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY;
                    break;
                case 6:
                    reservations->reservation[reservationIter].type = RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY;
                    break;
                case 7:
                    reservations->reservation[reservationIter].type = RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS;
                    break;
                case 8:
                    reservations->reservation[reservationIter].type = RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS;
                    break;
                default:
                    reservations->reservation[reservationIter].type = RES_TYPE_UNKNOWN;
                    break;
                }
            }
        }
        safe_free_aligned(&reservationKeys);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        // due to how the API was written and NVMe works, we need to call this instead to read all the keys. The get
        // reservations key count will only return 0 or 1 since there is at MOST 1 active reservation. But we need to go
        // through and find out how many keys are currently registered to loop through them later.
        uint16_t totalReservationKeys = UINT16_C(0);
        if (SUCCESS == get_Registration_Key_Count(device, &totalReservationKeys))
        {
            uint32_t reservationsLength = (C_CAST(uint32_t, totalReservationKeys) * UINT32_C(24)) + UINT32_C(24);
            uint8_t* reservationKeys    = C_CAST(
                uint8_t*, safe_calloc_aligned(reservationsLength, sizeof(uint8_t), device->os_info.minimumAlignment));
            if (reservationKeys == M_NULLPTR)
            {
                return MEMORY_FAILURE;
            }
            reservations->numberOfReservations = UINT16_C(0);
            if (SUCCESS == (ret = nvme_Reservation_Report(device, false, reservationKeys, reservationsLength)))
            {
                reservations->generation =
                    M_BytesTo4ByteValue(reservationKeys[3], reservationKeys[2], reservationKeys[1], reservationKeys[0]);
                if (reservationKeys[4] > 0 && numberReservations > 0)
                {
                    uint16_t reservationCount = M_BytesTo2ByteValue(reservationKeys[6], reservationKeys[5]);
                    if (reservationKeys[4] >
                        0) // if this doesn't have an active registration right now, no need to loop through.
                    {
                        // loop through and find the reservatio nthat is currently active.
                        for (uint32_t reservationIter = UINT32_C(0), offset = UINT32_C(24);
                             reservationIter < reservationCount && offset < reservationsLength;
                             offset += UINT32_C(24), ++reservationIter)
                        {
                            if (reservationKeys[2] & BIT0)
                            {
                                reservations->reservation[0].reservationKey =
                                    M_BytesTo8ByteValue(reservationKeys[offset + 23], reservationKeys[offset + 22],
                                                        reservationKeys[offset + 21], reservationKeys[offset + 20],
                                                        reservationKeys[offset + 19], reservationKeys[offset + 18],
                                                        reservationKeys[offset + 17], reservationKeys[offset + 16]);
                                reservations->numberOfReservations = 1;
                                reservations->reservation[0].scope =
                                    RESERVATION_SCOPE_LOGICAL_UNIT; // scope is always zero on NVMe (at least for now)
                                switch (reservationKeys[4])
                                {
                                case 0:
                                    reservations->reservation[0].type =
                                        RES_TYPE_NO_RESERVATION; // or unknown? THis is reserved in NVMe
                                    break;
                                case 1:
                                    reservations->reservation[0].type = RES_TYPE_WRITE_EXCLUSIVE;
                                    break;
                                case 2:
                                    reservations->reservation[0].type = RES_TYPE_EXCLUSIVE_ACCESS;
                                    break;
                                case 3:
                                    reservations->reservation[0].type = RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY;
                                    break;
                                case 4:
                                    reservations->reservation[0].type = RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY;
                                    break;
                                case 5:
                                    reservations->reservation[0].type = RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS;
                                    break;
                                case 6:
                                    reservations->reservation[0].type = RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS;
                                    break;
                                default:
                                    reservations->reservation[0].type = RES_TYPE_UNKNOWN;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            safe_free_aligned(&reservationKeys);
        }
    }
    return ret;
}

void show_Reservations(ptrReservationsData reservations)
{
    if (reservations->version >= RESERVATION_DATA_VERSION_V1 && reservations->size >= sizeof(reservationsDataV1))
    {
        print_str("Reservations:\n");
        print_str("=============\n");
        printf("Generation: %" PRIu32 "\n", reservations->generation);
        print_str("      Key        | Scope |        Type        \n");
        for (uint32_t resIter = UINT32_C(0); resIter < UINT16_MAX && resIter < reservations->numberOfReservations;
             ++resIter)
        {
#define RES_SCOPE_BUF_LEN 9
            DECLARE_ZERO_INIT_ARRAY(char, scopeBuf, RES_SCOPE_BUF_LEN);
            char* scope = &scopeBuf[0];
#define RES_TYPE_BUF_LEN 23
            DECLARE_ZERO_INIT_ARRAY(char, typeBuf, RES_TYPE_BUF_LEN);
            char* type = &typeBuf[0];
            switch (reservations->reservation[resIter].scope)
            {
            case RESERVATION_SCOPE_LOGICAL_UNIT:
                snprintf_err_handle(scope, RES_SCOPE_BUF_LEN, "LU");
                break;
            case RESERVATION_SCOPE_EXTENT:
                snprintf_err_handle(scope, RES_SCOPE_BUF_LEN, "Extent");
                break;
            case RESERVATION_SCOPE_ELEMENT:
                snprintf_err_handle(scope, RES_SCOPE_BUF_LEN, "Element");
                break;
            case RESERVATION_SCOPE_UNKNOWN:
            default:
                snprintf_err_handle(scope, RES_SCOPE_BUF_LEN, "Unknown");
                break;
            }
            switch (reservations->reservation[resIter].type)
            {
            case RES_TYPE_NO_RESERVATION:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "None");
                break;
            case RES_TYPE_READ_SHARED:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Read Shared");
                break;
            case RES_TYPE_WRITE_EXCLUSIVE:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Write Exclusive");
                break;
            case RES_TYPE_READ_EXCLUSIVE:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Read Exclusive");
                break;
            case RES_TYPE_EXCLUSIVE_ACCESS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Exclusive Access");
                break;
            case RES_TYPE_SHARED_ACCESS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Shared Access");
                break;
            case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Write Exclusive - RO");
                break;
            case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Exclusive Access - RO");
                break;
            case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Write Exclusive - AR");
                break;
            case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Exclusive Access - AR");
                break;
            case RES_TYPE_UNKNOWN:
            default:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Unknown");
                break;
            }
            printf("%16" PRIX64 "h  %7s  %20s", reservations->reservation[resIter].reservationKey, scope, type);
        }
        if (reservations->numberOfReservations == 0)
        {
            print_str("No active reservations.\n");
        }
    }
}

eReturnValues get_Full_Status_Key_Count(const tDevice* device, uint16_t* keyCount)
{
    eReturnValues ret = NOT_SUPPORTED;

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

    *keyCount = UINT16_C(0);
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        uint32_t fullStatusDataLength = UINT32_C(32); // some drive FW have a bug where if this is read as 8 bytes, it
                                                      // returns that there are no keys, even when there are...-TJE
        uint8_t* fullStatusData = C_CAST(
            uint8_t*, safe_calloc_aligned(fullStatusDataLength, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (fullStatusData == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        // check SCSI version and fall back if "new enough" but command doesn't complete successfully???
        if (device->drive_info.scsiVersion >= SCSI_VERSION_SPC_3 &&
            SUCCESS == (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_FULL_STATUS,
                                                         C_CAST(uint16_t, M_Min(fullStatusDataLength, UINT16_MAX)),
                                                         fullStatusData)))
        {
            // since the transport ID can vary in size, we cannot calculate this on length alone, so we need to re-read
            // with the full length of the data just reported and count them.
            fullStatusDataLength =
                8 + M_BytesTo4ByteValue(fullStatusData[4], fullStatusData[5], fullStatusData[6], fullStatusData[7]);
            // reallocate with enough memory
            safe_free_aligned(&fullStatusData);
            fullStatusData = C_CAST(
                uint8_t*, safe_calloc_aligned(fullStatusDataLength, sizeof(uint8_t), device->os_info.minimumAlignment));
            if (fullStatusData == M_NULLPTR)
            {
                return MEMORY_FAILURE;
            }
            // reread the data
            if (SUCCESS == (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_FULL_STATUS,
                                                             C_CAST(uint16_t, M_Min(fullStatusDataLength, UINT16_MAX)),
                                                             fullStatusData)))
            {
                // loop through each descriptor to count them.
                // each will be at least 24 bytes before the transport ID
                uint32_t offsetAdditionalLength = UINT32_C(0);
                for (uint32_t offset = UINT32_C(8); offset < fullStatusDataLength;
                     offset += 24 + offsetAdditionalLength)
                {
                    offsetAdditionalLength =
                        M_BytesTo4ByteValue(fullStatusData[offset + 20], fullStatusData[offset + 21],
                                            fullStatusData[offset + 22], fullStatusData[offset + 23]);
                    ++(*keyCount);
                }
            }
        }
        else // full status not supported or an old device
        {
            // use the registration key count function instead
            ret = get_Registration_Key_Count(device, keyCount);
        }
        safe_free_aligned(&fullStatusData);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(
            uint8_t, readKeyCount,
            24); // may be able to get away with only 8 bytes, but this read up until the list begins - TJE
        if (SUCCESS == (ret = nvme_Reservation_Report(device, false, readKeyCount, 24)))
        {
            *keyCount = M_BytesTo2ByteValue(readKeyCount[6], readKeyCount[5]);
        }
    }
    return ret;
}

typedef struct s_fullReservationKeyInfoV1
{
    uint64_t          key;
    bool              allTargetPorts;
    bool              reservationHolder; // NOTE: This must be set to true for scope and type to be valid
    uint16_t          relativeTargetPortIdentifier;
    eReservationScope scope;
    eReservationType  type;
    uint32_t          transportIDLength;
    uint8_t transportID[24]; // NOTE: This is 24 bytes as that is the common size. iSCSI is variable in size, so it will
                             // be truncated in this case -TJE
} fullReservationKeyInfoV1;

#define FULL_RESERVATION_INFO_VERSION_V1 1

typedef struct s_fullReservationInfoV1
{
    size_t   size;
    uint32_t version;
    uint32_t generation;
    uint16_t numberOfKeys;
    fullReservationKeyInfoV1
        reservationKey[1]; // Variable size depending on how many will be reported by the device at a given time.
} fullReservationInfoV1, *ptrFullReservationInfoV1;

eReturnValues get_Full_Status(const tDevice* device, uint16_t numberOfKeys, ptrFullReservationInfo fullReservation)
{
    // if newer SPC, use the read full status subcommand.
    // If older SPC, use the get_Registrations and get_Reservations functions to get all the data we need to collect. -
    // TJE
    eReturnValues ret = NOT_SUPPORTED;

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

    if (!(fullReservation->version >= FULL_RESERVATION_INFO_VERSION_V1) &&
        !(fullReservation->size >= sizeof(fullReservationInfoV1)))
    {
        return BAD_PARAMETER;
    }
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        uint32_t fullStatusDataLength = UINT32_C(32); // some drive FW have a bug where if this is read as 8 bytes, it
                                                      // returns that there are no keys, even when there are...-TJE
        uint8_t* fullStatusData = C_CAST(
            uint8_t*, safe_calloc_aligned(fullStatusDataLength, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (fullStatusData == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        // check SCSI version and fall back if "new enough" but command doesn't complete successfully???
        if (device->drive_info.scsiVersion >= SCSI_VERSION_SPC_3 &&
            SUCCESS == (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_FULL_STATUS,
                                                         C_CAST(uint16_t, M_Min(fullStatusDataLength, UINT16_MAX)),
                                                         fullStatusData)))
        {
            // since the transport ID can vary in size, we cannot calculate this on length alone, so we need to re-read
            // with the full length of the data just reported and count them.
            fullStatusDataLength =
                8 + M_BytesTo4ByteValue(fullStatusData[4], fullStatusData[5], fullStatusData[6], fullStatusData[7]);
            // reallocate with enough memory
            safe_free_aligned(&fullStatusData);
            fullStatusData = C_CAST(
                uint8_t*, safe_calloc_aligned(fullStatusDataLength, sizeof(uint8_t), device->os_info.minimumAlignment));
            if (fullStatusData == M_NULLPTR)
            {
                return MEMORY_FAILURE;
            }
            // reread the data
            if (SUCCESS == (ret = scsi_Persistent_Reserve_In(device, SCSI_PERSISTENT_RESERVE_IN_READ_FULL_STATUS,
                                                             C_CAST(uint16_t, M_Min(fullStatusDataLength, UINT16_MAX)),
                                                             fullStatusData)))
            {
                fullReservation->generation =
                    M_BytesTo4ByteValue(fullStatusData[0], fullStatusData[1], fullStatusData[2], fullStatusData[3]);

                // loop through each descriptor to count them.
                // each will be at least 24 bytes before the transport ID
                uint32_t offsetAdditionalLength = UINT32_C(0);
                for (uint32_t offset = UINT32_C(8), keyIter = UINT32_C(0);
                     offset < fullStatusDataLength && keyIter < numberOfKeys;
                     offset += UINT32_C(24) + offsetAdditionalLength, ++keyIter)
                {
                    offsetAdditionalLength =
                        M_BytesTo4ByteValue(fullStatusData[offset + 20], fullStatusData[offset + 21],
                                            fullStatusData[offset + 22], fullStatusData[offset + 23]);
                    // set the data to return
                    ++fullReservation->numberOfKeys;
                    fullReservation->reservationKey[keyIter].key = M_BytesTo8ByteValue(
                        fullStatusData[offset + 0], fullStatusData[offset + 1], fullStatusData[offset + 2],
                        fullStatusData[offset + 3], fullStatusData[offset + 4], fullStatusData[offset + 5],
                        fullStatusData[offset + 6], fullStatusData[offset + 7]);
                    fullReservation->reservationKey[keyIter].allTargetPorts =
                        M_ToBool(fullStatusData[offset + 12] & BIT1);
                    fullReservation->reservationKey[keyIter].reservationHolder =
                        M_ToBool(fullStatusData[offset + 12] & BIT0);
                    fullReservation->reservationKey[keyIter].relativeTargetPortIdentifier =
                        M_BytesTo2ByteValue(fullStatusData[offset + 18], fullStatusData[offset + 19]);
                    switch (get_bit_range_uint8(fullStatusData[offset + 13], 7, 4))
                    {
                    case 0:
                        fullReservation->reservationKey[keyIter].scope = RESERVATION_SCOPE_LOGICAL_UNIT;
                        break;
                    case 1:
                        fullReservation->reservationKey[keyIter].scope = RESERVATION_SCOPE_EXTENT;
                        break;
                    case 2:
                        fullReservation->reservationKey[keyIter].scope = RESERVATION_SCOPE_ELEMENT;
                        break;
                    default:
                        fullReservation->reservationKey[keyIter].scope = RESERVATION_SCOPE_UNKNOWN;
                        break;
                    }
                    switch (get_bit_range_uint8(fullStatusData[offset + 13], 3, 0))
                    {
                    case 0:
                        if (fullReservation->reservationKey[keyIter].reservationHolder)
                        {
                            fullReservation->reservationKey[keyIter].type = RES_TYPE_READ_SHARED;
                        }
                        else
                        {
                            fullReservation->reservationKey[keyIter].type = RES_TYPE_NO_RESERVATION;
                        }
                        break;
                    case 1:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_WRITE_EXCLUSIVE;
                        break;
                    case 2:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_READ_EXCLUSIVE;
                        break;
                    case 3:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_EXCLUSIVE_ACCESS;
                        break;
                    case 4:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_SHARED_ACCESS;
                        break;
                    case 5:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY;
                        break;
                    case 6:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY;
                        break;
                    case 7:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS;
                        break;
                    case 8:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS;
                        break;
                    default:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_UNKNOWN;
                        break;
                    }
                    fullReservation->reservationKey[keyIter].transportIDLength = offsetAdditionalLength;
                    if (offsetAdditionalLength > 0)
                    {
                        // copy the transport ID, if any, up to 24 bytes
                        safe_memcpy(fullReservation->reservationKey[keyIter].transportID, 24,
                                    &fullStatusData[offset + 24], M_Min(24, offsetAdditionalLength));
                    }
                    else
                    {
                        safe_memset(fullReservation->reservationKey[keyIter].transportID, 24, 0, 24);
                    }
                }
            }
        }
        else
        {
            // Older drive, or just doesn't support the full status capability, so we can read registrations and
            // reservations and then combine the two outputs into the expected full status results. read registrations
            // and reservations to match things up to the "equivalent" of the full status.
            uint16_t registrationCount = UINT16_C(0);
            uint16_t reservationCount  = UINT16_C(0);
            if (SUCCESS == get_Registration_Key_Count(device, &registrationCount) &&
                SUCCESS == get_Reservation_Count(device, &reservationCount))
            {
                // start with reading registrations first, then if the reservation count is > 0 read and map that
                // information too.
                ptrRegistrationKeysData registrations = C_CAST(
                    ptrRegistrationKeysData,
                    safe_calloc(sizeof(registrationKeysData) + registrationCount * sizeof(uint64_t), sizeof(uint8_t)));
                ptrReservationsData reservations =
                    C_CAST(ptrReservationsData,
                           safe_calloc(sizeof(reservationsData) + reservationCount * sizeof(reservationInfo),
                                       sizeof(uint8_t)));
                if (!registrations || !reservations)
                {
                    // not sure which of these failed, but these macros should be safe to use to make sure we don't
                    // leave any memory out there
                    safe_free_registration_key_data(&registrations);
                    safe_free_reservation_data(&reservations);
                    return MEMORY_FAILURE;
                }
                registrations->size    = sizeof(registrationKeysData);
                registrations->version = REGISTRATION_KEY_DATA_VERSION;
                reservations->size     = sizeof(reservationsData);
                reservations->version  = RESERVATION_DATA_VERSION;
                if (SUCCESS == (ret = get_Registration_Keys(device, registrationCount, registrations)) &&
                    SUCCESS == (ret = get_Reservations(device, reservationCount, reservations)))
                {
                    // got both things we need to map to the full data as best we can...some parts may be omitted, but
                    // that's OK...it's the best we can do right now. registrations will hold all the keys. reservations
                    // will have a list of the keys with an active reservation. So match this to the registration key to
                    // set the r_holder bit and set scope and type...everything else will be left at zero first fill in
                    // the keys from registrations
                    fullReservation->generation = registrations->generation;
                    for (uint32_t keyIter = UINT32_C(0);
                         keyIter < registrationCount && keyIter < registrations->numberOfKeys && keyIter < numberOfKeys;
                         ++keyIter)
                    {
                        // initialize the following to zeros since they don't map
                        fullReservation->reservationKey[keyIter].allTargetPorts               = false;
                        fullReservation->reservationKey[keyIter].relativeTargetPortIdentifier = 0;
                        safe_memset(fullReservation->reservationKey[keyIter].transportID, 24, 0, 24);
                        fullReservation->reservationKey[keyIter].transportIDLength = 0;
                        // initialize the following fields before we check the reservations data
                        fullReservation->reservationKey[keyIter].reservationHolder = false;
                        fullReservation->reservationKey[keyIter].scope             = RESERVATION_SCOPE_LOGICAL_UNIT;
                        fullReservation->reservationKey[keyIter].type              = RES_TYPE_NO_RESERVATION;
                        // set the key
                        fullReservation->reservationKey[keyIter].key = registrations->registrationKey[keyIter];
                        // check if this key matches any reported in the reservations data and add more fields from
                        // that...
                        for (uint32_t resKeyIter = UINT32_C(0);
                             resKeyIter < reservationCount && resKeyIter < reservations->numberOfReservations;
                             ++resKeyIter)
                        {
                            if (registrations->registrationKey[keyIter] ==
                                reservations->reservation[resKeyIter].reservationKey)
                            {
                                // found a matching key, so update remaining fields.
                                fullReservation->reservationKey[keyIter].reservationHolder = true;
                                fullReservation->reservationKey[keyIter].scope =
                                    reservations->reservation[resKeyIter].scope;
                                fullReservation->reservationKey[keyIter].type =
                                    reservations->reservation[resKeyIter].type;
                                break;
                            }
                        }
                        ++(fullReservation->numberOfKeys);
                    }
                }
            }
            else
            {
                ret = FAILURE;
            }
        }
        safe_free_aligned(&fullStatusData);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        uint32_t nvmeFullDataLen = UINT32_C(24) + (UINT32_C(24) * C_CAST(uint32_t, numberOfKeys));
        uint8_t* nvmeFullData    = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(nvmeFullDataLen, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (nvmeFullData == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        if (SUCCESS == (ret = nvme_Reservation_Report(device, false, nvmeFullData, nvmeFullDataLen)))
        {
            uint16_t keyCount = M_BytesTo2ByteValue(nvmeFullData[6], nvmeFullData[5]);
            fullReservation->generation =
                M_BytesTo4ByteValue(nvmeFullData[3], nvmeFullData[2], nvmeFullData[1], nvmeFullData[0]);
            for (uint32_t keyIter = UINT32_C(0), offset = UINT32_C(24); keyIter < keyCount && keyIter < numberOfKeys;
                 ++keyIter, offset += UINT32_C(24))
            {
                fullReservation->reservationKey[keyIter].relativeTargetPortIdentifier =
                    M_BytesTo2ByteValue(nvmeFullData[offset + 1], nvmeFullData[offset + 2]);
                fullReservation->reservationKey[keyIter].scope =
                    RESERVATION_SCOPE_LOGICAL_UNIT; // all nvme scops are like this
                fullReservation->reservationKey[keyIter].allTargetPorts    = true;
                fullReservation->reservationKey[keyIter].reservationHolder = M_ToBool(nvmeFullData[offset + 2] & BIT2);
                if (fullReservation->reservationKey[keyIter].reservationHolder)
                {
                    // set the reservation scope and type...only 1 reservation allowed at a time per namespace, so check
                    // offset 4 for this information.
                    switch (nvmeFullData[4])
                    {
                    case 0:
                        fullReservation->reservationKey[keyIter].type =
                            RES_TYPE_NO_RESERVATION; // or unknown? THis is reserved in NVMe
                        break;
                    case 1:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_WRITE_EXCLUSIVE;
                        break;
                    case 2:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_EXCLUSIVE_ACCESS;
                        break;
                    case 3:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY;
                        break;
                    case 4:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY;
                        break;
                    case 5:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS;
                        break;
                    case 6:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS;
                        break;
                    default:
                        fullReservation->reservationKey[keyIter].type = RES_TYPE_UNKNOWN;
                        break;
                    }
                }
                else
                {
                    // no reservation active
                    fullReservation->reservationKey[keyIter].type = RES_TYPE_NO_RESERVATION;
                }
                // host id/transport id
                safe_memcpy(fullReservation->reservationKey[keyIter].transportID, 24, &nvmeFullData[offset + 8], 8);
                fullReservation->reservationKey[keyIter].transportIDLength = 8;
                // finally, the key
                fullReservation->reservationKey[keyIter].key =
                    M_BytesTo8ByteValue(nvmeFullData[offset + 23], nvmeFullData[offset + 22], nvmeFullData[offset + 21],
                                        nvmeFullData[offset + 20], nvmeFullData[offset + 19], nvmeFullData[offset + 18],
                                        nvmeFullData[offset + 17], nvmeFullData[offset + 16]);
            }
        }
        safe_free_aligned(&nvmeFullData);
    }
    return ret;
}

void show_Full_Status(ptrFullReservationInfo fullReservation)
{

    if (fullReservation != M_NULLPTR && fullReservation->version >= FULL_RESERVATION_INFO_VERSION_V1 &&
        fullReservation->size >= sizeof(fullReservationInfoV1))
    {
        print_str("Full Reservation Status:\n");
        printf("\tGeneration: %" PRIX32 "h\n", fullReservation->generation);

        print_str("      Key        | ATP | Res Holder | Scope |         Type         |  RTPID  | Transport ID \n");
        for (uint32_t keyIter = UINT32_C(0); keyIter < UINT16_MAX && keyIter < fullReservation->numberOfKeys; ++keyIter)
        {
            char atp       = 'N';
            char resHolder = 'N';
            DECLARE_ZERO_INIT_ARRAY(char, scopeBuf, RES_SCOPE_BUF_LEN);
            char* scope = &scopeBuf[0];
            DECLARE_ZERO_INIT_ARRAY(char, typeBuf, RES_TYPE_BUF_LEN);
            char* type = &typeBuf[0];
            if (fullReservation->reservationKey[keyIter].allTargetPorts)
            {
                atp = 'Y';
            }
            if (fullReservation->reservationKey[keyIter].reservationHolder)
            {
                resHolder = 'Y';
            }
            switch (fullReservation->reservationKey[keyIter].scope)
            {
            case RESERVATION_SCOPE_LOGICAL_UNIT:
                snprintf_err_handle(type, RES_SCOPE_BUF_LEN, "LU");
                break;
            case RESERVATION_SCOPE_EXTENT:
                snprintf_err_handle(type, RES_SCOPE_BUF_LEN, "Extent");
                break;
            case RESERVATION_SCOPE_ELEMENT:
                snprintf_err_handle(type, RES_SCOPE_BUF_LEN, "Element");
                break;
            case RESERVATION_SCOPE_UNKNOWN:
            default:
                snprintf_err_handle(type, RES_SCOPE_BUF_LEN, "Unknown");
                break;
            }
            switch (fullReservation->reservationKey[keyIter].type)
            {
            case RES_TYPE_NO_RESERVATION:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "None");
                break;
            case RES_TYPE_READ_SHARED:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Read Shared");
                break;
            case RES_TYPE_WRITE_EXCLUSIVE:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Write Exclusive");
                break;
            case RES_TYPE_READ_EXCLUSIVE:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Read Exclusive");
                break;
            case RES_TYPE_EXCLUSIVE_ACCESS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Exclusive Access");
                break;
            case RES_TYPE_SHARED_ACCESS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Shared Access");
                break;
            case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Write Exclusive - RO");
                break;
            case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Exclusive Access - RO");
                break;
            case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Write Exclusive - AR");
                break;
            case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Exclusive Access - AR");
                break;
            case RES_TYPE_UNKNOWN:
            default:
                snprintf_err_handle(type, RES_TYPE_BUF_LEN, "Unknown");
                break;
            }
            printf("%16" PRIX64 "h  %c        %c      %7s  %23s  %08" PRIX16 "h ",
                   fullReservation->reservationKey[keyIter].key, atp, resHolder, scope, type,
                   fullReservation->reservationKey[keyIter].relativeTargetPortIdentifier);
            if (fullReservation->reservationKey[keyIter].transportIDLength > 0)
            {
                for (uint32_t transportIDoffset = UINT32_C(0);
                     transportIDoffset < 24 &&
                     transportIDoffset <= fullReservation->reservationKey[keyIter].transportIDLength;
                     ++transportIDoffset)
                {
                    printf("%02" PRIX8, fullReservation->reservationKey[keyIter].transportID[transportIDoffset]);
                }
                if (fullReservation->reservationKey[keyIter].transportIDLength > 24)
                {
                    print_str("...");
                }
                print_str("h");
            }
            else
            {
                print_str("     N/A");
            }
            print_str("\n");
        }
        if (fullReservation->numberOfKeys == 0)
        {
            print_str("No reservations or registration keys to report.\n");
        }
    }
    else
    {
        print_str("ERROR: Invalid full status structure version or size.\n");
    }
}

typedef struct s_persistentReserveOutBasic
{
    uint64_t reservationKey;
    uint64_t serviceActionReservationKey;
    uint32_t scopeSpecificAddress; // OBSOLETE, for use with older devices
    bool     activatePersistThroughPowerLoss;
    bool     allTargetPorts;
    bool     specifyInitiatorPorts;
    uint16_t extentLength;      // OBSOLETE, for use with older extent reservations
    uint32_t transportIDLength; // length of the following buffer
    uint8_t  transportID[1]; // overallocate to add transport IDs. Length is not needed in this data and is set from the
                             // parameter above
} persistentReserveOutBasic, *ptrPersistentReserveOutBasic;

#define PR_OUT_BASIC_MIN_LENGTH 24
M_NONNULL_IF_NONZERO_PARAM(1, 2)
M_PARAM_RW_SIZE(1, 2)
static void format_Basic_Info(uint8_t* M_NULLABLE                    ptrData,
                              uint32_t                               dataLength,
                              ptrPersistentReserveOutBasic M_NONNULL basicInfo)
{

    if (ptrData != M_NULLPTR && dataLength >= PR_OUT_BASIC_MIN_LENGTH &&
        basicInfo != M_NULLPTR) // 24 is minimum length for this buffer
    {
        // reservation key
        ptrData[0] = M_Byte7(basicInfo->reservationKey);
        ptrData[1] = M_Byte6(basicInfo->reservationKey);
        ptrData[2] = M_Byte5(basicInfo->reservationKey);
        ptrData[3] = M_Byte4(basicInfo->reservationKey);
        ptrData[4] = M_Byte3(basicInfo->reservationKey);
        ptrData[5] = M_Byte2(basicInfo->reservationKey);
        ptrData[6] = M_Byte1(basicInfo->reservationKey);
        ptrData[7] = M_Byte0(basicInfo->reservationKey);
        // service action reservation key
        ptrData[8]  = M_Byte7(basicInfo->serviceActionReservationKey);
        ptrData[9]  = M_Byte6(basicInfo->serviceActionReservationKey);
        ptrData[10] = M_Byte5(basicInfo->serviceActionReservationKey);
        ptrData[11] = M_Byte4(basicInfo->serviceActionReservationKey);
        ptrData[12] = M_Byte3(basicInfo->serviceActionReservationKey);
        ptrData[13] = M_Byte2(basicInfo->serviceActionReservationKey);
        ptrData[14] = M_Byte1(basicInfo->serviceActionReservationKey);
        ptrData[15] = M_Byte0(basicInfo->serviceActionReservationKey);
        //(obsolete) scope specific address
        ptrData[16] = M_Byte3(basicInfo->scopeSpecificAddress);
        ptrData[17] = M_Byte2(basicInfo->scopeSpecificAddress);
        ptrData[18] = M_Byte1(basicInfo->scopeSpecificAddress);
        ptrData[19] = M_Byte0(basicInfo->scopeSpecificAddress);
        // clear our 20 & 21, then set the bits needed
        ptrData[20] = UINT8_C(0);
        ptrData[21] = UINT8_C(0);
        if (basicInfo->activatePersistThroughPowerLoss)
        {
            ptrData[20] |= BIT0;
        }
        if (basicInfo->allTargetPorts)
        {
            ptrData[20] |= BIT2;
        }
        if (basicInfo->specifyInitiatorPorts)
        {
            ptrData[20] |= BIT3;
        }
        //(obsolete) extent length
        ptrData[22] = M_Byte1(basicInfo->extentLength);
        ptrData[23] = M_Byte0(basicInfo->extentLength);
        // additional info (transport IDs)
        if (dataLength > PR_OUT_BASIC_MIN_LENGTH && basicInfo->transportIDLength > 0 &&
            (basicInfo->transportIDLength +
             PR_OUT_BASIC_MIN_LENGTH /*length of basic data buffer before transport IDs*/ +
             4 /*for the length that is set before the transport ids start */) <= dataLength)
        {
            ptrData[24] = M_Byte3(basicInfo->transportIDLength);
            ptrData[25] = M_Byte2(basicInfo->transportIDLength);
            ptrData[26] = M_Byte1(basicInfo->transportIDLength);
            ptrData[27] = M_Byte0(basicInfo->transportIDLength);
            // now copy remaining data to the buffer...already checked the size above
            safe_memcpy(&ptrData[28], dataLength, basicInfo->transportID,
                        M_Min(dataLength - 28, basicInfo->transportIDLength));
        }
    }
}

eReturnValues register_Key(const tDevice* device,
                           uint64_t       registrationKey,
                           bool           allTargetPorts,
                           bool           persistThroughPowerLoss,
                           bool           ignoreExisting)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, registerData, PR_OUT_BASIC_MIN_LENGTH);
        persistentReserveOutBasic prData;
        safe_memset(&prData, sizeof(persistentReserveOutBasic), 0, sizeof(persistentReserveOutBasic));
        prData.reservationKey                  = 0; // when registering, set this to zero to begin.
        prData.serviceActionReservationKey     = registrationKey;
        prData.allTargetPorts                  = allTargetPorts;
        prData.activatePersistThroughPowerLoss = persistThroughPowerLoss;
        // NOTE: Not currently supporting the specifiy initiator ports field and transport IDs, but the data formatter
        // does support creating that buffer.

        format_Basic_Info(registerData, PR_OUT_BASIC_MIN_LENGTH, &prData);
        ret = scsi_Persistent_Reserve_Out(device,
                                          ignoreExisting ? SCSI_PERSISTENT_RESERVE_OUT_REGISTER_AND_IGNORE_EXISTING_KEY
                                                         : SCSI_PERSISTENT_RESERVE_OUT_REGISTER,
                                          0, 0, PR_OUT_BASIC_MIN_LENGTH, registerData);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, registerData, 16);
        registerData[8]  = M_Byte0(registrationKey);
        registerData[9]  = M_Byte1(registrationKey);
        registerData[10] = M_Byte2(registrationKey);
        registerData[11] = M_Byte3(registrationKey);
        registerData[12] = M_Byte4(registrationKey);
        registerData[13] = M_Byte5(registrationKey);
        registerData[14] = M_Byte6(registrationKey);
        registerData[15] = M_Byte7(registrationKey);
        // NOTE: SCSI translation mentions sending set features first if persist through power loss is not already
        // activated...not sure if this is needed or not in this case since translation is not being used...don't really
        // see it in the spec- TJE
        ret = nvme_Reservation_Register(device, persistThroughPowerLoss ? 3 : 2, ignoreExisting, 0, registerData,
                                        16); // TODO: Move values to enums/definitions
    }
    return ret;
}

eReturnValues unregister_Key(const tDevice* device, uint64_t currentRegistrationKey)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, registerData, PR_OUT_BASIC_MIN_LENGTH);
        persistentReserveOutBasic prData;
        safe_memset(&prData, sizeof(persistentReserveOutBasic), 0, sizeof(persistentReserveOutBasic));
        prData.reservationKey              = currentRegistrationKey;
        prData.serviceActionReservationKey = 0;
        format_Basic_Info(registerData, PR_OUT_BASIC_MIN_LENGTH, &prData);
        ret = scsi_Persistent_Reserve_Out(device, SCSI_PERSISTENT_RESERVE_OUT_REGISTER, 0, 0, PR_OUT_BASIC_MIN_LENGTH,
                                          registerData);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, registerData, 16);
        registerData[0] = M_Byte0(currentRegistrationKey);
        registerData[1] = M_Byte1(currentRegistrationKey);
        registerData[2] = M_Byte2(currentRegistrationKey);
        registerData[3] = M_Byte3(currentRegistrationKey);
        registerData[4] = M_Byte4(currentRegistrationKey);
        registerData[5] = M_Byte5(currentRegistrationKey);
        registerData[6] = M_Byte6(currentRegistrationKey);
        registerData[7] = M_Byte7(currentRegistrationKey);
        ret = nvme_Reservation_Register(device, 0, 0, 1, registerData, 16); // TODO: Move values to enums/definitions
    }
    return ret;
}

eReturnValues acquire_Reservation(const tDevice* device, uint64_t key, eReservationType resType)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, acquireRes, PR_OUT_BASIC_MIN_LENGTH);
        uint8_t                   scsiReservationType = UINT8_C(0);
        persistentReserveOutBasic prData;
        safe_memset(&prData, sizeof(persistentReserveOutBasic), 0, sizeof(persistentReserveOutBasic));
        prData.reservationKey = key;
        format_Basic_Info(acquireRes, PR_OUT_BASIC_MIN_LENGTH, &prData);
        switch (resType)
        {
        case RES_TYPE_READ_SHARED:
            scsiReservationType = 0;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE:
            scsiReservationType = 1;
            break;
        case RES_TYPE_READ_EXCLUSIVE: // obsolete - old scsi only
            scsiReservationType = 2;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS:
            scsiReservationType = 3;
            break;
        case RES_TYPE_SHARED_ACCESS: // obsolete - old SCSI only
            scsiReservationType = 4;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
            scsiReservationType = 5;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
            scsiReservationType = 6;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
            scsiReservationType = 7;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
            scsiReservationType = 8;
            break;
        case RES_TYPE_NO_RESERVATION:
        default:
            return BAD_PARAMETER;
        }
        ret = scsi_Persistent_Reserve_Out(device, SCSI_PERSISTENT_RESERVE_OUT_RESERVE, 0, scsiReservationType,
                                          PR_OUT_BASIC_MIN_LENGTH, acquireRes);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, acquireRes, 16);
        uint8_t nvmeReservationType = UINT8_C(0);
        acquireRes[0]               = M_Byte0(key);
        acquireRes[1]               = M_Byte1(key);
        acquireRes[2]               = M_Byte2(key);
        acquireRes[3]               = M_Byte3(key);
        acquireRes[4]               = M_Byte4(key);
        acquireRes[5]               = M_Byte5(key);
        acquireRes[6]               = M_Byte6(key);
        acquireRes[7]               = M_Byte7(key);
        switch (resType)
        {
        case RES_TYPE_WRITE_EXCLUSIVE:
            nvmeReservationType = 1;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS:
            nvmeReservationType = 2;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
            nvmeReservationType = 3;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
            nvmeReservationType = 4;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
            nvmeReservationType = 5;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
            nvmeReservationType = 6;
            break;
        case RES_TYPE_NO_RESERVATION:
        default:
            return BAD_PARAMETER;
        }
        ret = nvme_Reservation_Acquire(device, nvmeReservationType, false, 0 /*acquire*/, acquireRes, 16);
    }
    return ret;
}

eReturnValues release_Reservation(const tDevice* device, uint64_t key, eReservationType resType)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, releaseRes, PR_OUT_BASIC_MIN_LENGTH);
        uint8_t                   scsiReservationType = UINT8_C(0);
        persistentReserveOutBasic prData;
        safe_memset(&prData, sizeof(persistentReserveOutBasic), 0, sizeof(persistentReserveOutBasic));
        prData.reservationKey = key;
        format_Basic_Info(releaseRes, PR_OUT_BASIC_MIN_LENGTH, &prData);
        switch (resType)
        {
        case RES_TYPE_READ_SHARED:
            scsiReservationType = 0;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE:
            scsiReservationType = 1;
            break;
        case RES_TYPE_READ_EXCLUSIVE: // obsolete - old scsi only
            scsiReservationType = 2;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS:
            scsiReservationType = 3;
            break;
        case RES_TYPE_SHARED_ACCESS: // obsolete - old SCSI only
            scsiReservationType = 4;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
            scsiReservationType = 5;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
            scsiReservationType = 6;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
            scsiReservationType = 7;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
            scsiReservationType = 8;
            break;
        case RES_TYPE_NO_RESERVATION:
        default:
            return BAD_PARAMETER;
        }
        ret = scsi_Persistent_Reserve_Out(device, SCSI_PERSISTENT_RESERVE_OUT_RELEASE, 0, scsiReservationType,
                                          PR_OUT_BASIC_MIN_LENGTH, releaseRes);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, releaseRes, 8);
        uint8_t nvmeReservationType = UINT8_C(0);
        releaseRes[0]               = M_Byte0(key);
        releaseRes[1]               = M_Byte1(key);
        releaseRes[2]               = M_Byte2(key);
        releaseRes[3]               = M_Byte3(key);
        releaseRes[4]               = M_Byte4(key);
        releaseRes[5]               = M_Byte5(key);
        releaseRes[6]               = M_Byte6(key);
        releaseRes[7]               = M_Byte7(key);
        switch (resType)
        {
        case RES_TYPE_WRITE_EXCLUSIVE:
            nvmeReservationType = 1;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS:
            nvmeReservationType = 2;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
            nvmeReservationType = 3;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
            nvmeReservationType = 4;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
            nvmeReservationType = 5;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
            nvmeReservationType = 6;
            break;
        case RES_TYPE_NO_RESERVATION:
        default:
            return BAD_PARAMETER;
        }
        ret = nvme_Reservation_Release(device, nvmeReservationType, false, 0 /*release*/, releaseRes, 8);
    }
    return ret;
}

eReturnValues clear_Reservations(const tDevice* device, uint64_t key)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, clearRes, PR_OUT_BASIC_MIN_LENGTH);
        persistentReserveOutBasic prData;
        safe_memset(&prData, sizeof(persistentReserveOutBasic), 0, sizeof(persistentReserveOutBasic));
        prData.reservationKey = key;
        format_Basic_Info(clearRes, PR_OUT_BASIC_MIN_LENGTH, &prData);
        ret = scsi_Persistent_Reserve_Out(device, SCSI_PERSISTENT_RESERVE_OUT_CLEAR, 0, 0, PR_OUT_BASIC_MIN_LENGTH,
                                          clearRes);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, clearRes, 8);
        clearRes[0] = M_Byte0(key);
        clearRes[1] = M_Byte1(key);
        clearRes[2] = M_Byte2(key);
        clearRes[3] = M_Byte3(key);
        clearRes[4] = M_Byte4(key);
        clearRes[5] = M_Byte5(key);
        clearRes[6] = M_Byte6(key);
        clearRes[7] = M_Byte7(key);
        ret         = nvme_Reservation_Release(device, 0, false, 1 /*clear*/, clearRes, 8);
    }
    return ret;
}

eReturnValues preempt_Reservation(const tDevice*   device,
                                  uint64_t         key,
                                  uint64_t         preemptKey,
                                  bool             abort,
                                  eReservationType resType)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, preemptRes, PR_OUT_BASIC_MIN_LENGTH);
        uint8_t                   scsiReservationType = UINT8_C(0);
        persistentReserveOutBasic prData;
        safe_memset(&prData, sizeof(persistentReserveOutBasic), 0, sizeof(persistentReserveOutBasic));
        prData.reservationKey              = key;
        prData.serviceActionReservationKey = preemptKey;
        format_Basic_Info(preemptRes, PR_OUT_BASIC_MIN_LENGTH, &prData);
        switch (resType)
        {
        case RES_TYPE_READ_SHARED:
            scsiReservationType = 0;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE:
            scsiReservationType = 1;
            break;
        case RES_TYPE_READ_EXCLUSIVE: // obsolete - old scsi only
            scsiReservationType = 2;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS:
            scsiReservationType = 3;
            break;
        case RES_TYPE_SHARED_ACCESS: // obsolete - old SCSI only
            scsiReservationType = 4;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
            scsiReservationType = 5;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
            scsiReservationType = 6;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
            scsiReservationType = 7;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
            scsiReservationType = 8;
            break;
        case RES_TYPE_NO_RESERVATION:
        default:
            return BAD_PARAMETER;
        }
        ret = scsi_Persistent_Reserve_Out(
            device, abort ? SCSI_PERSISTENT_RESERVE_OUT_PREEMPT_AND_ABORT : SCSI_PERSISTENT_RESERVE_OUT_PREEMPT, 0,
            scsiReservationType, PR_OUT_BASIC_MIN_LENGTH, preemptRes);
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, preemptRes, 16);
        uint8_t nvmeReservationType = UINT8_C(0);
        preemptRes[0]               = M_Byte0(key);
        preemptRes[1]               = M_Byte1(key);
        preemptRes[2]               = M_Byte2(key);
        preemptRes[3]               = M_Byte3(key);
        preemptRes[4]               = M_Byte4(key);
        preemptRes[5]               = M_Byte5(key);
        preemptRes[6]               = M_Byte6(key);
        preemptRes[7]               = M_Byte7(key);
        preemptRes[8]               = M_Byte0(preemptKey);
        preemptRes[9]               = M_Byte1(preemptKey);
        preemptRes[10]              = M_Byte2(preemptKey);
        preemptRes[11]              = M_Byte3(preemptKey);
        preemptRes[12]              = M_Byte4(preemptKey);
        preemptRes[13]              = M_Byte5(preemptKey);
        preemptRes[14]              = M_Byte6(preemptKey);
        preemptRes[15]              = M_Byte7(preemptKey);
        switch (resType)
        {
        case RES_TYPE_WRITE_EXCLUSIVE:
            nvmeReservationType = 1;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS:
            nvmeReservationType = 2;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
            nvmeReservationType = 3;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
            nvmeReservationType = 4;
            break;
        case RES_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
            nvmeReservationType = 5;
            break;
        case RES_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
            nvmeReservationType = 6;
            break;
        case RES_TYPE_NO_RESERVATION:
        default:
            return BAD_PARAMETER;
        }
        ret = nvme_Reservation_Acquire(device, nvmeReservationType, false,
                                       abort ? 2 /*preempt & abort*/ : 1 /*preempt*/, preemptRes, 16);
    }
    return ret;
}

// TODO: replace lost reservation
// TODO: register and move: Requires transport ID which there is not a good way to deal with right now with how these
// tools work-TJE
