/// <summary>
        /// Checks whether token information matches PKCS#11 URI
        /// </summary>
        /// <param name="pkcs11Uri">PKCS#11 URI</param>
        /// <param name="tokenInfo">Token information</param>
        /// <returns>True if token information matches PKCS#11 URI</returns>
        public static bool Matches(Pkcs11Uri pkcs11Uri, LLA4.CK_TOKEN_INFO tokenInfo)
        {
            if (pkcs11Uri == null)
                throw new ArgumentNullException("pkcs11Uri");

            string token = ConvertUtils.BytesToUtf8String(tokenInfo.Label, true);
            string manufacturer = ConvertUtils.BytesToUtf8String(tokenInfo.ManufacturerId, true);
            string serial = ConvertUtils.BytesToUtf8String(tokenInfo.SerialNumber, true);
            string model = ConvertUtils.BytesToUtf8String(tokenInfo.Model, true);

            return Matches(pkcs11Uri, token, manufacturer, serial, model);
        }
        /// <summary>
        /// Checks whether slot information matches PKCS#11 URI
        /// </summary>
        /// <param name="pkcs11Uri">PKCS#11 URI</param>
        /// <param name="slotInfo">Slot information</param>
        /// <param name="slotId">Slot identifier</param>
        /// <returns>True if slot information matches PKCS#11 URI</returns>
        public static bool Matches(Pkcs11Uri pkcs11Uri, LLA4.CK_SLOT_INFO slotInfo, uint? slotId)
        {
            if (pkcs11Uri == null)
                throw new ArgumentNullException("pkcs11Uri");

            string manufacturer = ConvertUtils.BytesToUtf8String(slotInfo.ManufacturerId, true);
            string description = ConvertUtils.BytesToUtf8String(slotInfo.SlotDescription, true);

            return Matches(pkcs11Uri, manufacturer, description, slotId);
        }
        /// <summary>
        /// Returns list of object attributes defined by PKCS#11 URI
        /// </summary>
        /// <param name="pkcs11Uri">PKCS#11 URI</param>
        /// <param name="objectAttributes">List of object attributes defined by PKCS#11 URI</param>
        public static void GetObjectAttributes(Pkcs11Uri pkcs11Uri, out LLA4.CK_ATTRIBUTE[] objectAttributes)
        {
            if (pkcs11Uri == null)
                throw new ArgumentNullException("pkcs11Uri");

            List<LLA4.CK_ATTRIBUTE> attributes = null;

            if (pkcs11Uri.DefinesObject)
            {
                attributes = new List<LLA4.CK_ATTRIBUTE>();
                if (pkcs11Uri.Type != null)
                    attributes.Add(LLA4.CkaUtils.CreateAttribute(CKA.CKA_CLASS, pkcs11Uri.Type.Value));
                if (pkcs11Uri.Object != null)
                    attributes.Add(LLA4.CkaUtils.CreateAttribute(CKA.CKA_LABEL, pkcs11Uri.Object));
                if (pkcs11Uri.Id != null)
                    attributes.Add(LLA4.CkaUtils.CreateAttribute(CKA.CKA_ID, pkcs11Uri.Id));
            }

            objectAttributes = attributes.ToArray();
        }
        /// <summary>
        /// Checks whether PKCS#11 library information matches PKCS#11 URI
        /// </summary>
        /// <param name="pkcs11Uri">PKCS#11 URI</param>
        /// <param name="libraryInfo">PKCS#11 library information</param>
        /// <returns>True if PKCS#11 library information matches PKCS#11 URI</returns>
        public static bool Matches(Pkcs11Uri pkcs11Uri, LLA4.CK_INFO libraryInfo)
        {
            if (pkcs11Uri == null)
                throw new ArgumentNullException("pkcs11Uri");

            string manufacturer = ConvertUtils.BytesToUtf8String(libraryInfo.ManufacturerId, true);
            string description = ConvertUtils.BytesToUtf8String(libraryInfo.LibraryDescription, true);
            string version = ConvertUtils.CkVersionToString(libraryInfo.LibraryVersion);

            return Matches(pkcs11Uri, manufacturer, description, version);
        }
        /// <summary>
        /// Obtains a list of all slots where token that matches PKCS#11 URI is present
        /// </summary>
        /// <param name="pkcs11Uri">PKCS#11 URI</param>
        /// <param name="pkcs11">Low level PKCS#11 wrapper</param>
        /// <param name="tokenPresent">Flag indicating whether the list obtained includes only those slots with a token present (true), or all slots (false)</param>
        /// <param name="slotList">List of slots matching PKCS#11 URI</param>
        /// <returns>CKR_OK if successful; any other value otherwise</returns>
        public static CKR GetMatchingSlotList(Pkcs11Uri pkcs11Uri, LLA4.Pkcs11 pkcs11, bool tokenPresent, out uint[] slotList)
        {
            if (pkcs11Uri == null)
                throw new ArgumentNullException("pkcs11Uri");

            if (pkcs11 == null)
                throw new ArgumentNullException("pkcs11");

            List<uint> matchingSlots = new List<uint>();

            // Get library information
            LLA4.CK_INFO libraryInfo = new LLA4.CK_INFO();
            CKR rv = pkcs11.C_GetInfo(ref libraryInfo);
            if (rv != CKR.CKR_OK)
            {
                slotList = new uint[0];
                return rv;
            }

            // Check whether library matches URI
            if (!Matches(pkcs11Uri, libraryInfo))
            {
                slotList = new uint[0];
                return CKR.CKR_OK;
            }

            // Get number of slots in first call
            uint slotCount = 0;
            rv = pkcs11.C_GetSlotList(false, null, ref slotCount);
            if (rv != CKR.CKR_OK)
            {
                slotList = new uint[0];
                return rv;
            }

            if (slotCount < 1)
            {
                slotList = new uint[0];
                return CKR.CKR_OK;
            }

            // Allocate array for slot IDs
            uint[] slots = new uint[slotCount];

            // Get slot IDs in second call
            rv = pkcs11.C_GetSlotList(tokenPresent, slots, ref slotCount);
            if (rv != CKR.CKR_OK)
            {
                slotList = new uint[0];
                return rv;
            }

            // Shrink array if needed
            if (slots.Length != slotCount)
                Array.Resize(ref slots, Convert.ToInt32(slotCount));

            // Match slots with Pkcs11Uri
            foreach (uint slot in slots)
            {
                LLA4.CK_SLOT_INFO slotInfo = new LLA4.CK_SLOT_INFO();
                rv = pkcs11.C_GetSlotInfo(slot, ref slotInfo);
                if (rv != CKR.CKR_OK)
                {
                    slotList = new uint[0];
                    return rv;
                }

                // Check whether slot matches URI
                if (Matches(pkcs11Uri, slotInfo, slot))
                {
                    if ((slotInfo.Flags & CKF.CKF_TOKEN_PRESENT) == CKF.CKF_TOKEN_PRESENT)
                    {
                        LLA4.CK_TOKEN_INFO tokenInfo = new LLA4.CK_TOKEN_INFO();
                        rv = pkcs11.C_GetTokenInfo(slot, ref tokenInfo);
                        if (rv != CKR.CKR_OK)
                        {
                            slotList = new uint[0];
                            return rv;
                        }

                        // Check whether token matches URI
                        if (Matches(pkcs11Uri, tokenInfo))
                            matchingSlots.Add(slot);
                    }
                    else
                    {
                        if (!tokenPresent && Matches(pkcs11Uri, null, null, null, null))
                            matchingSlots.Add(slot);
                    }
                }
            }

            slotList = matchingSlots.ToArray();
            return CKR.CKR_OK;
        }