mod test_tr_from_tpm_public {
    use crate::common::create_ctx_without_session;
    use tss_esapi::{
        attributes::NvIndexAttributesBuilder,
        constants::{tss::TPM2_NV_INDEX_FIRST, CapabilityType},
        handles::{NvIndexHandle, NvIndexTpmHandle, ObjectHandle},
        interface_types::{
            algorithm::HashingAlgorithm,
            resource_handles::{NvAuth, Provision},
            session_handles::AuthSession,
        },
        structures::{Auth, CapabilityData, MaxNvBuffer, NvPublicBuilder},
        tss2_esys::TPM2_HANDLE,
        Context,
    };

    use std::convert::TryFrom;

    fn remove_nv_index_handle_from_tpm(nv_index_tpm_handle: NvIndexTpmHandle, nv_auth: Provision) {
        let mut context = create_ctx_without_session();
        let mut property = TPM2_NV_INDEX_FIRST;
        while let Ok((capability_data, more_data_available)) =
            context.get_capability(CapabilityType::Handles, property, 1)
        {
            if let CapabilityData::Handles(nv_index_handles) = capability_data {
                if let Some(&retrieved_nv_index_tpm_handle) = nv_index_handles.first() {
                    if retrieved_nv_index_tpm_handle == nv_index_tpm_handle.into() {
                        let handle = context
                            .tr_from_tpm_public(nv_index_tpm_handle.into())
                            .map(NvIndexHandle::from)
                            .expect("Failed to get nv index from tpm");
                        context.execute_with_session(Some(AuthSession::Password), |ctx| {
                            ctx.nv_undefine_space(nv_auth, handle)
                                .expect("Failed to undefine space");
                        });
                        return;
                    }

                    if more_data_available {
                        property = TPM2_HANDLE::from(retrieved_nv_index_tpm_handle) + 1;
                    }
                }
            }

            if !more_data_available {
                return;
            }
        }
    }

    // Need to set the shEnable in the TPMA_STARTUP in order for this to work.
    #[ignore]
    #[test]
    fn test_tr_from_tpm_public_owner_auth() {
        let mut context = create_ctx_without_session();

        let nv_index_tpm_handle = NvIndexTpmHandle::new(0x01500021).unwrap();

        // closure for cleaning up if a call fails.
        let cleanup = |context: &mut Context,
                       e: tss_esapi::Error,
                       handle: NvIndexHandle,
                       fn_name: &str|
         -> tss_esapi::Error {
            // Set password authorization
            let _ = context.nv_undefine_space(Provision::Owner, handle).unwrap();
            panic!("{} failed: {}", fn_name, e);
        };

        // Create nv public.
        let nv_index_attributes = NvIndexAttributesBuilder::new()
            .with_owner_write(true)
            .with_owner_read(true)
            .build()
            .expect("Failed to create owner nv index attributes");

        let nv_public = NvPublicBuilder::new()
            .with_nv_index(nv_index_tpm_handle)
            .with_index_name_algorithm(HashingAlgorithm::Sha256)
            .with_index_attributes(nv_index_attributes)
            .with_data_area_size(32)
            .build()
            .unwrap();

        let initial_nv_index_handle = context
            .nv_define_space(Provision::Owner, None, nv_public)
            .unwrap();
        ///////////////////////////////////////////
        // Read the name from the tpm
        let (_expected_nv_public, expected_name) = context
            .nv_read_public(initial_nv_index_handle)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "nv_read_public"))
            .unwrap();
        ////////////////////////////////////////////////
        // Close the handle
        let mut handle_to_be_closed: ObjectHandle = initial_nv_index_handle.into();
        context
            .tr_close(&mut handle_to_be_closed)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "tr_close"))
            .unwrap();
        assert_eq!(handle_to_be_closed, ObjectHandle::None);
        ////////////////////////////////////////////////
        // Make Esys create a new ObjectHandle from the
        // data in the TPM.
        let new_nv_index_handle = context
            .tr_from_tpm_public(nv_index_tpm_handle.into())
            .expect("tr_from_tpm_public failed");
        ///////////////////////////////////////////////
        // Get name of the object using the new handle
        let actual_name = context
            .tr_get_name(new_nv_index_handle)
            .map_err(|e| cleanup(&mut context, e, new_nv_index_handle.into(), "tr_get_name"))
            .unwrap();
        //////////////////////////////////////////////
        // Remove undefine the space
        let _ = context
            .nv_undefine_space(Provision::Owner, new_nv_index_handle.into())
            .unwrap();

        assert_eq!(expected_name, actual_name);
    }

    #[test]
    fn test_tr_from_tpm_public_password_auth() {
        let nv_index_tpm_handle = NvIndexTpmHandle::new(0x01500022).unwrap();
        remove_nv_index_handle_from_tpm(nv_index_tpm_handle, Provision::Owner);

        let mut context = create_ctx_without_session();

        let auth = Auth::try_from(vec![
            10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
        ])
        .expect("Failed to create auth");

        // closure for cleaning up if a call fails.
        let cleanup = |context: &mut Context,
                       e: tss_esapi::Error,
                       handle: NvIndexHandle,
                       fn_name: &str|
         -> tss_esapi::Error {
            // Set password authorization
            context.set_sessions((Some(AuthSession::Password), None, None));
            let _ = context
                .nv_undefine_space(Provision::Owner, handle)
                .expect("Failed to call nv_undefine_space");
            panic!("{} failed: {}", fn_name, e);
        };

        // Create nv public.
        let nv_index_attributes = NvIndexAttributesBuilder::new()
            .with_auth_write(true)
            .with_auth_read(true)
            .build()
            .expect("Failed to create auth nv index attributes");

        let nv_public = NvPublicBuilder::new()
            .with_nv_index(nv_index_tpm_handle)
            .with_index_name_algorithm(HashingAlgorithm::Sha256)
            .with_index_attributes(nv_index_attributes)
            .with_data_area_size(32)
            .build()
            .expect("Failed to build nv public");
        ///////////////////////////////////////////////////////////////
        // Define space
        //
        // Set password authorization when creating the space.
        context.set_sessions((Some(AuthSession::Password), None, None));
        let initial_nv_index_handle = context
            .nv_define_space(Provision::Owner, Some(auth), nv_public)
            .expect("Failed to call nv_define_space");
        ///////////////////////////////////////////////////////////////
        // Read the name from the tpm
        //
        // No password authorization.
        context.clear_sessions();
        let (_expected_nv_public, expected_name) = context
            .nv_read_public(initial_nv_index_handle)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "nv_read_public"))
            .expect("Failed to call nv_read_public");
        ///////////////////////////////////////////////////////////////
        // Close the esys handle (remove all meta data).
        //
        let mut handle_to_be_closed: ObjectHandle = initial_nv_index_handle.into();
        context
            .tr_close(&mut handle_to_be_closed)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "tr_close"))
            .expect("Failed to call tr_close");
        assert_eq!(handle_to_be_closed, ObjectHandle::None);
        // The value of the handle_to_be_closed will be set to a 'None' handle
        // if the operations was successful.

        ///////////////////////////////////////////////////////////////
        // Make Esys create a new ObjectHandle from the
        // data in the TPM.
        //
        // The handle is gone so if this fails it is not
        // possible to remove the defined space.
        let new_nv_index_handle = context
            .tr_from_tpm_public(nv_index_tpm_handle.into())
            .map_err(|e| -> tss_esapi::Result<ObjectHandle> {
                panic!("tr_from_tpm_public failed: {}", e);
            })
            .expect("Error when call tr_from_tpm_public");
        ///////////////////////////////////////////////////////////////
        // Get name of the object using the new handle
        //
        let actual_name = context
            .tr_get_name(new_nv_index_handle)
            .map_err(|e| cleanup(&mut context, e, new_nv_index_handle.into(), "tr_get_name"))
            .expect("Failed to call tr_get_name");
        ///////////////////////////////////////////////////////////////
        // Remove undefine the space
        //
        // Set password authorization
        context.set_sessions((Some(AuthSession::Password), None, None));
        let _ = context
            .nv_undefine_space(Provision::Owner, new_nv_index_handle.into())
            .expect("Failed to call nv_undefine_space");
        ///////////////////////////////////////////////////////////////
        // Check that we got the correct name
        //
        assert_eq!(expected_name, actual_name);
    }

    #[test]
    fn read_from_retrieved_handle_using_password_authorization() {
        let nv_index_tpm_handle = NvIndexTpmHandle::new(0x01500023).unwrap();

        remove_nv_index_handle_from_tpm(nv_index_tpm_handle, Provision::Owner);

        let mut context = create_ctx_without_session();

        let auth = Auth::try_from(vec![
            10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
        ])
        .unwrap();

        // closure for cleaning up if a call fails.
        let cleanup = |context: &mut Context,
                       e: tss_esapi::Error,
                       handle: NvIndexHandle,
                       fn_name: &str|
         -> tss_esapi::Error {
            // Set password authorization
            context.set_sessions((Some(AuthSession::Password), None, None));
            let _ = context.nv_undefine_space(Provision::Owner, handle).unwrap();
            panic!("{} failed: {}", fn_name, e);
        };

        // Create nv public. Only use auth for write.
        let nv_index_attributes = NvIndexAttributesBuilder::new()
            .with_auth_write(true)
            .with_auth_read(true)
            .build()
            .expect("Failed to create auth nv index attributes");

        let nv_public = NvPublicBuilder::new()
            .with_nv_index(nv_index_tpm_handle)
            .with_index_name_algorithm(HashingAlgorithm::Sha256)
            .with_index_attributes(nv_index_attributes)
            .with_data_area_size(32)
            .build()
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // Define space
        //
        // Set password authorization when creating the space.
        context.set_sessions((Some(AuthSession::Password), None, None));
        let initial_nv_index_handle = context
            .nv_define_space(Provision::Owner, Some(auth.clone()), nv_public)
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // Read the name from the tpm
        //
        // No password authorization.
        context.clear_sessions();
        let (_expected_nv_public, initial_name) = context
            .nv_read_public(initial_nv_index_handle)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "nv_read_public"))
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // Write data to created index.
        //
        // When the write succeeds the attributes will change
        // and there for the name will change.
        let expected_data = MaxNvBuffer::try_from(vec![
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
            24, 25, 26, 27, 28, 29, 30, 31,
        ])
        .unwrap();
        context.set_sessions((Some(AuthSession::Password), None, None));
        context
            .nv_write(
                NvAuth::NvIndex(initial_nv_index_handle),
                initial_nv_index_handle,
                expected_data.clone(),
                0,
            )
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "nv_write"))
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // Read the new name that have been calculated after the write.
        //
        // No password authorization.
        context.clear_sessions();
        let (_expected_nv_public, expected_name) = context
            .nv_read_public(initial_nv_index_handle)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "nv_read_public"))
            .unwrap();
        assert_ne!(initial_name, expected_name);
        ///////////////////////////////////////////////////////////////
        // Close the esys handle (remove all meta data).
        //
        let mut handle_to_be_closed: ObjectHandle = initial_nv_index_handle.into();
        context
            .tr_close(&mut handle_to_be_closed)
            .map_err(|e| cleanup(&mut context, e, initial_nv_index_handle, "tr_close"))
            .unwrap();
        assert_eq!(handle_to_be_closed, ObjectHandle::None);
        // The value of the handle_to_be_closed will be set to a 'None' handle
        // if the operations was successful.

        ///////////////////////////////////////////////////////////////
        // Make Esys create a new ObjectHandle from the
        // data in the TPM.
        //
        // The handle is gone so if this fails it is not
        // possible to remove the defined space.
        let new_nv_index_handle = context
            .tr_from_tpm_public(nv_index_tpm_handle.into())
            .map(NvIndexHandle::from)
            .expect("tr_from_tpm_public failed: {}");
        ///////////////////////////////////////////////////////////////
        // Get name of the object using the new handle
        //
        let actual_name = context
            .tr_get_name(new_nv_index_handle.into())
            .map_err(|e| cleanup(&mut context, e, new_nv_index_handle, "tr_get_name"))
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // Call nv_read to get data from nv_index.
        //

        // Set authorization for the retrieved handle
        context
            .tr_set_auth(new_nv_index_handle.into(), auth)
            .map_err(|e| cleanup(&mut context, e, new_nv_index_handle, "tr_set_auth"))
            .unwrap();
        // read the data
        context.set_sessions((Some(AuthSession::Password), None, None));
        let actual_data = context
            .nv_read(
                NvAuth::NvIndex(new_nv_index_handle),
                new_nv_index_handle,
                32,
                0,
            )
            .map_err(|e| cleanup(&mut context, e, new_nv_index_handle, "nv_read"))
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // Remove undefine the space
        //
        // Set password authorization
        context.set_sessions((Some(AuthSession::Password), None, None));
        let _ = context
            .nv_undefine_space(Provision::Owner, new_nv_index_handle)
            .unwrap();
        ///////////////////////////////////////////////////////////////
        // The name will have changed
        //
        assert_eq!(expected_name, actual_name);
        assert_eq!(expected_data, actual_data);
    }
}
