BlinkConfig/UICKeyChainStore.m
//
//  UICKeyChainStore.m
//  UICKeyChainStore
//
//  Created by Kishikawa Katsumi on 11/11/20.
//  Copyright (c) 2011 Kishikawa Katsumi. All rights reserved.
//

#import "UICKeyChainStore.h"

NSString * const UICKeyChainStoreErrorDomain = @"com.kishikawakatsumi.uickeychainstore";
static NSString *_defaultService;

@interface UICKeyChainStore ()

@end

@implementation UICKeyChainStore

+ (NSString *)defaultService
{
    if (!_defaultService) {
        _defaultService = [[NSBundle mainBundle] bundleIdentifier] ?: @"";
    }
    
    return _defaultService;
}

+ (void)setDefaultService:(NSString *)defaultService
{
    _defaultService = defaultService;
}

#pragma mark -

+ (UICKeyChainStore *)keyChainStore
{
    return [[self alloc] initWithService:nil accessGroup:nil];
}

+ (UICKeyChainStore *)keyChainStoreWithService:(NSString *)service
{
    return [[self alloc] initWithService:service accessGroup:nil];
}

+ (UICKeyChainStore *)keyChainStoreWithService:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [[self alloc] initWithService:service accessGroup:accessGroup];
}

#pragma mark -

+ (UICKeyChainStore *)keyChainStoreWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType
{
    return [[self alloc] initWithServer:server protocolType:protocolType authenticationType:UICKeyChainStoreAuthenticationTypeDefault];
}

+ (UICKeyChainStore *)keyChainStoreWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType authenticationType:(UICKeyChainStoreAuthenticationType)authenticationType
{
    return [[self alloc] initWithServer:server protocolType:protocolType authenticationType:authenticationType];
}

#pragma mark -

- (instancetype)init
{
    return [self initWithService:[self.class defaultService] accessGroup:nil];
}

- (instancetype)initWithService:(NSString *)service
{
    return [self initWithService:service accessGroup:nil];
}

- (instancetype)initWithService:(NSString *)service accessGroup:(NSString *)accessGroup
{
    self = [super init];
    if (self) {
        _itemClass = UICKeyChainStoreItemClassGenericPassword;
        
        if (!service) {
            service = [self.class defaultService];
        }
        _service = service.copy;
        _accessGroup = accessGroup.copy;
        [self commonInit];
    }
    
    return self;
}

#pragma mark -

- (instancetype)initWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType
{
    return [self initWithServer:server protocolType:protocolType authenticationType:UICKeyChainStoreAuthenticationTypeDefault];
}

- (instancetype)initWithServer:(NSURL *)server protocolType:(UICKeyChainStoreProtocolType)protocolType authenticationType:(UICKeyChainStoreAuthenticationType)authenticationType
{
    self = [super init];
    if (self) {
        _itemClass = UICKeyChainStoreItemClassInternetPassword;
        
        _server = server.copy;
        _protocolType = protocolType;
        _authenticationType = authenticationType;
        
        [self commonInit];
    }
    
    return self;
}

#pragma mark -

- (void)commonInit
{
    _accessibility = UICKeyChainStoreAccessibilityAfterFirstUnlock;
    _useAuthenticationUI = YES;
}

#pragma mark -

+ (NSString *)stringForKey:(NSString *)key
{
    return [self stringForKey:key service:nil accessGroup:nil error:nil];
}

+ (NSString *)stringForKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self stringForKey:key service:nil accessGroup:nil error:error];
}

+ (NSString *)stringForKey:(NSString *)key service:(NSString *)service
{
    return [self stringForKey:key service:service accessGroup:nil error:nil];
}

+ (NSString *)stringForKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
{
    return [self stringForKey:key service:service accessGroup:nil error:error];
}

+ (NSString *)stringForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [self stringForKey:key service:service accessGroup:accessGroup error:nil];
}

+ (NSString *)stringForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
{
    if (!key) {
        NSError *e = [self argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
        if (error) {
            *error = e;
        }
        return nil;
    }
    if (!service) {
        service = [self defaultService];
    }
    
    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
    return [keychain stringForKey:key error:error];
}

#pragma mark -

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key
{
    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:nil error:nil];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:nil error:error];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key genericAttribute:(id)genericAttribute
{
    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:nil];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    return [self setString:value forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:error];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service
{
    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:nil error:nil];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
{
    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:nil error:error];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute
{
    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:nil];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    return [self setString:value forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:error];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [self setString:value forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:nil];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
{
    return [self setString:value forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:error];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute
{
    return [self setString:value forKey:key service:service accessGroup:accessGroup genericAttribute:genericAttribute error:nil];
}

+ (BOOL)setString:(NSString *)value forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    if (!value) {
        return [self removeItemForKey:key service:service accessGroup:accessGroup error:error];
    }
    NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:genericAttribute error:error];
    }
    NSError *e = [self conversionError:NSLocalizedString(@"failed to convert string to data", nil)];
    if (error) {
        *error = e;
    }
    return NO;
}

#pragma mark -

+ (NSData *)dataForKey:(NSString *)key
{
    return [self dataForKey:key service:nil accessGroup:nil error:nil];
}

+ (NSData *)dataForKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self dataForKey:key service:nil accessGroup:nil error:error];
}

+ (NSData *)dataForKey:(NSString *)key service:(NSString *)service
{
    return [self dataForKey:key service:service accessGroup:nil error:nil];
}

+ (NSData *)dataForKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
{
    return [self dataForKey:key service:service accessGroup:nil error:error];
}

+ (NSData *)dataForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [self dataForKey:key service:service accessGroup:accessGroup error:nil];
}

+ (NSData *)dataForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
{
    if (!key) {
        NSError *e = [self argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
        if (error) {
            *error = e;
        }
        return nil;
    }
    if (!service) {
        service = [self defaultService];
    }
    
    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
    return [keychain dataForKey:key error:error];
}

#pragma mark -

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key
{
    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:nil error:nil];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:nil error:error];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute
{
    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:nil];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    return [self setData:data forKey:key service:nil accessGroup:nil genericAttribute:genericAttribute error:error];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service
{
    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:nil error:nil];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
{
    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:nil error:error];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute
{
    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:nil];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    return [self setData:data forKey:key service:service accessGroup:nil genericAttribute:genericAttribute error:error];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:nil];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
{
    return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:nil error:error];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute
{
    return [self setData:data forKey:key service:service accessGroup:accessGroup genericAttribute:genericAttribute error:nil];
}

+ (BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    if (!key) {
        NSError *e = [self argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
        if (error) {
            *error = e;
        }
        return NO;
    }
    if (!service) {
        service = [self defaultService];
    }
    
    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
    return [keychain setData:data forKey:key genericAttribute:genericAttribute];
}

#pragma mark -

- (BOOL)contains:(NSString *)key
{
    NSMutableDictionary *query = [self query];
    query[(__bridge __strong id)kSecAttrAccount] = key;

    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
    return status == errSecSuccess || status == errSecInteractionNotAllowed;
}

#pragma mark -

- (NSString *)stringForKey:(id)key
{
    return [self stringForKey:key error:nil];
}

- (NSString *)stringForKey:(id)key error:(NSError *__autoreleasing *)error
{
    NSData *data = [self dataForKey:key error:error];
    if (data) {
        NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (string) {
            return string;
        }
        NSError *e = [self.class conversionError:NSLocalizedString(@"failed to convert data to string", nil)];
        if (error) {
            *error = e;
        }
        return nil;
    }
    
    return nil;
}

#pragma mark -

- (BOOL)setString:(NSString *)string forKey:(NSString *)key
{
    return [self setString:string forKey:key genericAttribute:nil label:nil comment:nil error:nil];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self setString:string forKey:key genericAttribute:nil label:nil comment:nil error:error];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key genericAttribute:(id)genericAttribute
{
    return [self setString:string forKey:key genericAttribute:genericAttribute label:nil comment:nil error:nil];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    return [self setString:string forKey:key genericAttribute:genericAttribute label:nil comment:nil error:error];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment
{
    return [self setString:string forKey:key genericAttribute:nil label:label comment:comment error:nil];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
{
    return [self setString:string forKey:key genericAttribute:nil label:label comment:comment error:error];
}

- (BOOL)setString:(NSString *)string forKey:(NSString *)key genericAttribute:(id)genericAttribute label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
{
    if (!string) {
        return [self removeItemForKey:key error:error];
    }
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        return [self setData:data forKey:key genericAttribute:genericAttribute label:label comment:comment error:error];
    }
    NSError *e = [self.class conversionError:NSLocalizedString(@"failed to convert string to data", nil)];
    if (error) {
        *error = e;
    }
    return NO;
}

#pragma mark -

- (NSData *)dataForKey:(NSString *)key
{
    return [self dataForKey:key error:nil];
}

- (NSData *)dataForKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    NSMutableDictionary *query = [self query];
    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
    query[(__bridge __strong id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
    
    query[(__bridge __strong id)kSecAttrAccount] = key;
    
    CFTypeRef data = nil;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &data);
    
    if (status == errSecSuccess) {
        NSData *ret = [NSData dataWithData:(__bridge NSData *)data];
        if (data) {
            CFRelease(data);
            return ret;
        } else {
            NSError *e = [self.class unexpectedError:NSLocalizedString(@"Unexpected error has occurred.", nil)];
            if (error) {
                *error = e;
            }
            return nil;
        }
    } else if (status == errSecItemNotFound) {
        return nil;
    }
    
    NSError *e = [self.class securityError:status];
    if (error) {
        *error = e;
    }
    return nil;
}

#pragma mark -

- (BOOL)setData:(NSData *)data forKey:(NSString *)key
{
    return [self setData:data forKey:key genericAttribute:nil label:nil comment:nil error:nil];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self setData:data forKey:key genericAttribute:nil label:nil comment:nil error:error];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute
{
    return [self setData:data forKey:key genericAttribute:genericAttribute label:nil comment:nil error:nil];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute error:(NSError * __autoreleasing *)error
{
    return [self setData:data forKey:key genericAttribute:genericAttribute label:nil comment:nil error:error];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment
{
    return [self setData:data forKey:key genericAttribute:nil label:label comment:comment error:nil];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
{
    return [self setData:data forKey:key genericAttribute:nil label:label comment:comment error:error];
}

- (BOOL)setData:(NSData *)data forKey:(NSString *)key genericAttribute:(id)genericAttribute label:(NSString *)label comment:(NSString *)comment error:(NSError *__autoreleasing *)error
{
    if (!key) {
        NSError *e = [self.class argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
        if (error) {
            *error = e;
        }
        return NO;
    }
    if (!data) {
        return [self removeItemForKey:key error:error];
    }
    
    NSMutableDictionary *query = [self query];
    query[(__bridge __strong id)kSecAttrAccount] = key;
#if TARGET_OS_IOS
    if (floor(NSFoundationVersionNumber) > floor(1144.17)) { // iOS 9+
        query[(__bridge __strong id)kSecUseAuthenticationUI] = (__bridge id)kSecUseAuthenticationUIFail;
#if  __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
    } else if (floor(NSFoundationVersionNumber) > floor(1047.25)) { // iOS 8+
        query[(__bridge __strong id)kSecUseNoAuthenticationUI] = (__bridge id)kCFBooleanTrue;
#endif
    }
#elif TARGET_OS_WATCH || TARGET_OS_TV
    query[(__bridge __strong id)kSecUseAuthenticationUI] = (__bridge id)kSecUseAuthenticationUIFail;
#endif
    
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
    if (status == errSecSuccess || status == errSecInteractionNotAllowed) {
        query = [self query];
        query[(__bridge __strong id)kSecAttrAccount] = key;
        
        NSError *unexpectedError = nil;
        NSMutableDictionary *attributes = [self attributesWithKey:nil value:data error:&unexpectedError];
        
        if (genericAttribute) {
            attributes[(__bridge __strong id)kSecAttrGeneric] = genericAttribute;
        }
        if (label) {
            attributes[(__bridge __strong id)kSecAttrLabel] = label;
        }
        if (comment) {
            attributes[(__bridge __strong id)kSecAttrComment] = comment;
        }
        
        if (unexpectedError) {
            NSLog(@"error: [%@] %@", @(unexpectedError.code), NSLocalizedString(@"Unexpected error has occurred.", nil));
            if (error) {
                *error = unexpectedError;
            }
            return NO;
        } else {
            
            if (status == errSecInteractionNotAllowed && floor(NSFoundationVersionNumber) <= floor(1140.11)) { // iOS 8.0.x
                if ([self removeItemForKey:key error:error]) {
                    return [self setData:data forKey:key label:label comment:comment error:error];
                }
            } else {
                status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
            }
            if (status != errSecSuccess) {
                NSError *e = [self.class securityError:status];
                if (error) {
                    *error = e;
                }
                return NO;
            }
        }
    } else if (status == errSecItemNotFound) {
        NSError *unexpectedError = nil;
        NSMutableDictionary *attributes = [self attributesWithKey:key value:data error:&unexpectedError];
        
        if (genericAttribute) {
            attributes[(__bridge __strong id)kSecAttrGeneric] = genericAttribute;
        }
        if (label) {
            attributes[(__bridge __strong id)kSecAttrLabel] = label;
        }
        if (comment) {
            attributes[(__bridge __strong id)kSecAttrComment] = comment;
        }
        
        if (unexpectedError) {
            NSLog(@"error: [%@] %@", @(unexpectedError.code), NSLocalizedString(@"Unexpected error has occurred.", nil));
            if (error) {
                *error = unexpectedError;
            }
            return NO;
        } else {
            status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
            if (status != errSecSuccess) {
                NSError *e = [self.class securityError:status];
                if (error) {
                    *error = e;
                }
                return NO;
            }
        }
    } else {
        NSError *e = [self.class securityError:status];
        if (error) {
            *error = e;
        }
        return NO;
    }
    
    return YES;
}

#pragma mark -

+ (BOOL)removeItemForKey:(NSString *)key
{
    return [self removeItemForKey:key service:nil accessGroup:nil error:nil];
}

+ (BOOL)removeItemForKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    return [self removeItemForKey:key service:nil accessGroup:nil error:error];
}

+ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service
{
    return [self removeItemForKey:key service:service accessGroup:nil error:nil];
}

+ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service error:(NSError *__autoreleasing *)error
{
    return [self removeItemForKey:key service:service accessGroup:nil error:error];
}

+ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [self removeItemForKey:key service:service accessGroup:accessGroup error:nil];
}

+ (BOOL)removeItemForKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
{
    if (!key) {
        NSError *e = [self.class argumentError:NSLocalizedString(@"the key must not to be nil", nil)];
        if (error) {
            *error = e;
        }
        return NO;
    }
    if (!service) {
        service = [self defaultService];
    }
    
    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
    return [keychain removeItemForKey:key error:error];
}

#pragma mark -

+ (BOOL)removeAllItems
{
    return [self removeAllItemsForService:nil accessGroup:nil error:nil];
}

+ (BOOL)removeAllItemsWithError:(NSError *__autoreleasing *)error
{
    return [self removeAllItemsForService:nil accessGroup:nil error:error];
}

+ (BOOL)removeAllItemsForService:(NSString *)service
{
    return [self removeAllItemsForService:service accessGroup:nil error:nil];
}

+ (BOOL)removeAllItemsForService:(NSString *)service error:(NSError *__autoreleasing *)error
{
    return [self removeAllItemsForService:service accessGroup:nil error:error];
}

+ (BOOL)removeAllItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup
{
    return [self removeAllItemsForService:service accessGroup:accessGroup error:nil];
}

+ (BOOL)removeAllItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup error:(NSError *__autoreleasing *)error
{
    UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
    return [keychain removeAllItemsWithError:error];
}

#pragma mark -

- (BOOL)removeItemForKey:(NSString *)key
{
    return [self removeItemForKey:key error:nil];
}

- (BOOL)removeItemForKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    NSMutableDictionary *query = [self query];
    query[(__bridge __strong id)kSecAttrAccount] = key;
    
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
    if (status != errSecSuccess && status != errSecItemNotFound) {
        NSError *e = [self.class securityError:status];
        if (error) {
            *error = e;
        }
        return NO;
    }
    
    return YES;
}

#pragma mark -

- (BOOL)removeAllItems
{
    return [self removeAllItemsWithError:nil];
}

- (BOOL)removeAllItemsWithError:(NSError *__autoreleasing *)error
{
    NSMutableDictionary *query = [self query];
#if !TARGET_OS_IPHONE
    query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
#endif
    
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
    if (status != errSecSuccess && status != errSecItemNotFound) {
        NSError *e = [self.class securityError:status];
        if (error) {
            *error = e;
        }
        return NO;
    }
    
    return YES;
}

#pragma mark -

- (NSString *)objectForKeyedSubscript:(NSString <NSCopying> *)key
{
    return [self stringForKey:key];
}

- (void)setObject:(NSString *)obj forKeyedSubscript:(NSString <NSCopying> *)key
{
    if (!obj) {
        [self removeItemForKey:key];
    } else {
        [self setString:obj forKey:key];
    }
}

#pragma mark -

- (NSArray UIC_KEY_TYPE *)allKeys
{
    NSArray *items = [self.class prettify:[self itemClassObject] items:[self items]];
    NSMutableArray *keys = [[NSMutableArray alloc] init];
    for (NSDictionary *item in items) {
        NSString *key = item[@"key"];
        if (key) {
            [keys addObject:key];
        }
    }
    return keys.copy;
}

+ (NSArray UIC_KEY_TYPE *)allKeysWithItemClass:(UICKeyChainStoreItemClass)itemClass
{
    CFTypeRef itemClassObject = kSecClassGenericPassword;
    if (itemClass == UICKeyChainStoreItemClassGenericPassword) {
        itemClassObject = kSecClassGenericPassword;
    } else if (itemClass == UICKeyChainStoreItemClassInternetPassword) {
        itemClassObject = kSecClassInternetPassword;
    }
    
    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
    query[(__bridge __strong id)kSecClass] = (__bridge id)itemClassObject;
    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
    query[(__bridge __strong id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
    
    CFArrayRef result = nil;
    CFDictionaryRef cfquery = (CFDictionaryRef)CFBridgingRetain(query);
    OSStatus status = SecItemCopyMatching(cfquery, (CFTypeRef *)&result);
    CFRelease(cfquery);
    
    if (status == errSecSuccess) {
        NSArray *items = [self prettify:itemClassObject items:(__bridge NSArray *)result];
        NSMutableArray *keys = [[NSMutableArray alloc] init];
        for (NSDictionary *item in items) {
            if (itemClassObject == kSecClassGenericPassword) {
                [keys addObject:@{@"service": item[@"service"] ?: @"", @"key": item[@"key"] ?: @""}];
            } else if (itemClassObject == kSecClassInternetPassword) {
                [keys addObject:@{@"server": item[@"service"] ?: @"", @"key": item[@"key"] ?: @""}];
            }
        }
        return keys.copy;
    } else if (status == errSecItemNotFound) {
        return @[];
    }
    
    return nil;
}

+ (NSArray *)allItemsWithItemClass:(UICKeyChainStoreItemClass)itemClass
{
    CFTypeRef itemClassObject = kSecClassGenericPassword;
    if (itemClass == UICKeyChainStoreItemClassGenericPassword) {
        itemClassObject = kSecClassGenericPassword;
    } else if (itemClass == UICKeyChainStoreItemClassInternetPassword) {
        itemClassObject = kSecClassInternetPassword;
    }
    
    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
    query[(__bridge __strong id)kSecClass] = (__bridge id)itemClassObject;
    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
    query[(__bridge __strong id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
#if TARGET_OS_IPHONE
    query[(__bridge __strong id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
#endif
    
    CFArrayRef result = nil;
    CFDictionaryRef cfquery = (CFDictionaryRef)CFBridgingRetain(query);
    OSStatus status = SecItemCopyMatching(cfquery, (CFTypeRef *)&result);
    CFRelease(cfquery);
    
    if (status == errSecSuccess) {
        return [self prettify:itemClassObject items:(__bridge NSArray *)result];
    } else if (status == errSecItemNotFound) {
        return @[];
    }
    
    return nil;
}

- (NSArray *)allItems
{
    return [self.class prettify:[self itemClassObject] items:[self items]];
}

- (NSArray *)items
{
    NSMutableDictionary *query = [self query];
    query[(__bridge __strong id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
    query[(__bridge __strong id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
#if TARGET_OS_IPHONE
    query[(__bridge __strong id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
#endif
    
    CFArrayRef result = nil;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query,(CFTypeRef *)&result);
    
    if (status == errSecSuccess) {
        return CFBridgingRelease(result);
    } else if (status == errSecItemNotFound) {
        return @[];
    }
    
    return nil;
}

+ (NSArray *)prettify:(CFTypeRef)itemClass items:(NSArray *)items
{
    NSMutableArray *prettified = [[NSMutableArray alloc] init];
    
    for (NSDictionary *attributes in items) {
        NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
        if (itemClass == kSecClassGenericPassword) {
            item[@"class"] = @"GenericPassword";
            id service = attributes[(__bridge id)kSecAttrService];
            if (service) {
                item[@"service"] = service;
            }
            id accessGroup = attributes[(__bridge id)kSecAttrAccessGroup];
            if (accessGroup) {
                item[@"accessGroup"] = accessGroup;
            }
        } else if (itemClass == kSecClassInternetPassword) {
            item[@"class"] = @"InternetPassword";
            id server = attributes[(__bridge id)kSecAttrServer];
            if (server) {
                item[@"server"] = server;
            }
            id protocolType = attributes[(__bridge id)kSecAttrProtocol];
            if (protocolType) {
                item[@"protocol"] = protocolType;
            }
            id authenticationType = attributes[(__bridge id)kSecAttrAuthenticationType];
            if (authenticationType) {
                item[@"authenticationType"] = authenticationType;
            }
        }
        id key = attributes[(__bridge id)kSecAttrAccount];
        if (key) {
            item[@"key"] = key;
        }
        NSData *data = attributes[(__bridge id)kSecValueData];
        NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (string) {
            item[@"value"] = string;
        } else {
            item[@"value"] = data;
        }
        
        id accessible = attributes[(__bridge id)kSecAttrAccessible];
        if (accessible) {
            item[@"accessibility"] = accessible;
        }
        
        if (floor(NSFoundationVersionNumber) > floor(993.00)) { // iOS 7+
            id synchronizable = attributes[(__bridge id)kSecAttrSynchronizable];
            if (synchronizable) {
                item[@"synchronizable"] = synchronizable;
            }
        }
        
        [prettified addObject:item];
    }
    
    return prettified.copy;
}

#pragma mark -

- (void)setSynchronizable:(BOOL)synchronizable
{
    _synchronizable = synchronizable;
    if (_authenticationPolicy) {
        NSLog(@"%@", @"Cannot specify both an authenticationPolicy and a synchronizable");
    }
}

- (void)setAccessibility:(UICKeyChainStoreAccessibility)accessibility authenticationPolicy:(UICKeyChainStoreAuthenticationPolicy)authenticationPolicy
{
    _accessibility = accessibility;
    _authenticationPolicy = authenticationPolicy;
    if (_synchronizable) {
        NSLog(@"%@", @"Cannot specify both an authenticationPolicy and a synchronizable");
    }
}

#pragma mark -

#if TARGET_OS_IOS
- (void)sharedPasswordWithCompletion:(void (^)(NSString *account, NSString *password, NSError *error))completion
{
    NSString *domain = self.server.host;
    if (domain.length > 0) {
        [self.class requestSharedWebCredentialForDomain:domain account:nil completion:^(NSArray *credentials, NSError *error) {
            NSDictionary *credential = credentials.firstObject;
            if (credential) {
                NSString *account = credential[@"account"];
                NSString *password = credential[@"password"];
                if (completion) {
                    completion(account, password, error);
                }
            } else {
                if (completion) {
                    completion(nil, nil, error);
                }
            }
        }];
    } else {
        NSError *error = [self.class argumentError:NSLocalizedString(@"the server property must not to be nil, should use 'keyChainStoreWithServer:protocolType:' initializer to instantiate keychain store", nil)];
        if (completion) {
            completion(nil, nil, error);
        }
    }
}

- (void)sharedPasswordForAccount:(NSString *)account completion:(void (^)(NSString *password, NSError *error))completion
{
    NSString *domain = self.server.host;
    if (domain.length > 0) {
        [self.class requestSharedWebCredentialForDomain:domain account:account completion:^(NSArray *credentials, NSError *error) {
            NSDictionary *credential = credentials.firstObject;
            if (credential) {
                NSString *password = credential[@"password"];
                if (completion) {
                    completion(password, error);
                }
            } else {
                if (completion) {
                    completion(nil, error);
                }
            }
        }];
    } else {
        NSError *error = [self.class argumentError:NSLocalizedString(@"the server property must not to be nil, should use 'keyChainStoreWithServer:protocolType:' initializer to instantiate keychain store", nil)];
        if (completion) {
            completion(nil, error);
        }
    }
}

- (void)setSharedPassword:(NSString *)password forAccount:(NSString *)account completion:(void (^)(NSError *error))completion
{
    NSString *domain = self.server.host;
    if (domain.length > 0) {
        SecAddSharedWebCredential((__bridge CFStringRef)domain, (__bridge CFStringRef)account, (__bridge CFStringRef)password, ^(CFErrorRef error) {
            if (completion) {
                completion((__bridge NSError *)error);
            }
        });
    } else {
        NSError *error = [self.class argumentError:NSLocalizedString(@"the server property must not to be nil, should use 'keyChainStoreWithServer:protocolType:' initializer to instantiate keychain store", nil)];
        if (completion) {
            completion(error);
        }
    }
}

- (void)removeSharedPasswordForAccount:(NSString *)account completion:(void (^)(NSError *error))completion
{
    [self setSharedPassword:nil forAccount:account completion:completion];
}

+ (void)requestSharedWebCredentialWithCompletion:(void (^)(NSArray UIC_CREDENTIAL_TYPE *credentials, NSError *error))completion
{
    [self requestSharedWebCredentialForDomain:nil account:nil completion:completion];
}

+ (void)requestSharedWebCredentialForDomain:(NSString *)domain account:(NSString *)account completion:(void (^)(NSArray UIC_CREDENTIAL_TYPE *credentials, NSError *error))completion
{
    SecRequestSharedWebCredential((__bridge CFStringRef)domain, (__bridge CFStringRef)account, ^(CFArrayRef credentials, CFErrorRef error) {
        if (error) {
            NSError *e = (__bridge NSError *)error;
            if (e.code != errSecItemNotFound) {
                NSLog(@"error: [%@] %@", @(e.code), e.localizedDescription);
            }
        }
        
        NSMutableArray *sharedCredentials = [[NSMutableArray alloc] init];
        for (NSDictionary *credential in (__bridge NSArray *)credentials) {
            NSMutableDictionary *sharedCredential = [[NSMutableDictionary alloc] init];
            NSString *server = credential[(__bridge __strong id)kSecAttrServer];
            if (server) {
                sharedCredential[@"server"] = server;
            }
            NSString *account = credential[(__bridge __strong id)kSecAttrAccount];
            if (account) {
                sharedCredential[@"account"] = account;
            }
            NSString *password = credential[(__bridge __strong id)kSecSharedPassword];
            if (password) {
                sharedCredential[@"password"] = password;
            }
            [sharedCredentials addObject:sharedCredential];
        }
        
        if (completion) {
            completion(sharedCredentials.copy, (__bridge NSError *)error);
        }
    });
}

+ (NSString *)generatePassword
{
    return (NSString *)CFBridgingRelease(SecCreateSharedWebCredentialPassword());
}

#endif

#pragma mark -

- (NSString *)description
{
    NSArray *items = [self allItems];
    if (items.count == 0) {
        return @"()";
    }
    NSMutableString *description = [[NSMutableString alloc] initWithString:@"(\n"];
    for (NSDictionary *item in items) {
        [description appendFormat:@"    %@", item];
    }
    [description appendString:@")"];
    return description.copy;
}

- (NSString *)debugDescription
{
    return [NSString stringWithFormat:@"%@", [self items]];
}

#pragma mark -

- (NSMutableDictionary *)query
{
    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
    
    CFTypeRef itemClass = [self itemClassObject];
    query[(__bridge __strong id)kSecClass] =(__bridge id)itemClass;
    if (floor(NSFoundationVersionNumber) > floor(993.00)) { // iOS 7+ (NSFoundationVersionNumber_iOS_6_1)
        query[(__bridge __strong id)kSecAttrSynchronizable] = (__bridge id)kSecAttrSynchronizableAny;
    }
    
    if (itemClass == kSecClassGenericPassword) {
        query[(__bridge __strong id)(kSecAttrService)] = _service;
#if !TARGET_OS_SIMULATOR
        if (_accessGroup) {
            query[(__bridge __strong id)kSecAttrAccessGroup] = _accessGroup;
        }
#endif
    } else {
        if (_server.host) {
            query[(__bridge __strong id)kSecAttrServer] = _server.host;
        }
        if (_server.port) {
            query[(__bridge __strong id)kSecAttrPort] = _server.port;
        }
        CFTypeRef protocolTypeObject = [self protocolTypeObject];
        if (protocolTypeObject) {
            query[(__bridge __strong id)kSecAttrProtocol] = (__bridge id)protocolTypeObject;
        }
        CFTypeRef authenticationTypeObject = [self authenticationTypeObject];
        if (authenticationTypeObject) {
            query[(__bridge __strong id)kSecAttrAuthenticationType] = (__bridge id)authenticationTypeObject;
        }
    }
    
#if TARGET_OS_IOS
    if (_authenticationPrompt) {
        if (floor(NSFoundationVersionNumber) > floor(1047.25)) { // iOS 8+ (NSFoundationVersionNumber_iOS_7_1)
            query[(__bridge __strong id)kSecUseOperationPrompt] = _authenticationPrompt;
        } else {
            NSLog(@"%@", @"Unavailable 'authenticationPrompt' attribute on iOS versions prior to 8.0.");
        }
    }
#endif

    if (!_useAuthenticationUI) {
#if TARGET_OS_IOS
        if (floor(NSFoundationVersionNumber) > floor(1144.17)) { // iOS 9+
            query[(__bridge __strong id)kSecUseAuthenticationUI] = (__bridge id)kSecUseAuthenticationUIFail;
#if  __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
        } else if (floor(NSFoundationVersionNumber) > floor(1047.25)) { // iOS 8+
            query[(__bridge __strong id)kSecUseNoAuthenticationUI] = (__bridge id)kCFBooleanTrue;
#endif
        }
#elif TARGET_OS_WATCH || TARGET_OS_TV
        query[(__bridge __strong id)kSecUseAuthenticationUI] = (__bridge id)kSecUseAuthenticationUIFail;
#endif
    }
    
    return query;
}

- (NSMutableDictionary *)attributesWithKey:(NSString *)key value:(NSData *)value error:(NSError *__autoreleasing *)error
{
    NSMutableDictionary *attributes;
    
    if (key) {
        attributes = [self query];
        attributes[(__bridge __strong id)kSecAttrAccount] = key;
    } else {
        attributes = [[NSMutableDictionary alloc] init];
    }
    
    attributes[(__bridge __strong id)kSecValueData] = value;
    
#if TARGET_OS_IOS
    double iOS_7_1_or_10_9_2 = 1047.25; // NSFoundationVersionNumber_iOS_7_1
#else
    double iOS_7_1_or_10_9_2 = 1056.13; // NSFoundationVersionNumber10_9_2
#endif
    CFTypeRef accessibilityObject = [self accessibilityObject];
    if (_authenticationPolicy && accessibilityObject) {
        if (floor(NSFoundationVersionNumber) > floor(iOS_7_1_or_10_9_2)) { // iOS 8+ or OS X 10.10+
            CFErrorRef securityError = NULL;
            SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessibilityObject, (SecAccessControlCreateFlags)_authenticationPolicy, &securityError);
            if (securityError) {
                NSError *e = (__bridge NSError *)securityError;
                NSLog(@"error: [%@] %@", @(e.code), e.localizedDescription);
                if (error) {
                    *error = e;
                    CFRelease(accessControl);
                    return nil;
                }
            }
            if (!accessControl) {
                NSString *message = NSLocalizedString(@"Unexpected error has occurred.", nil);
                NSError *e = [self.class unexpectedError:message];
                if (error) {
                    *error = e;
                }
                return nil;
            }
            attributes[(__bridge __strong id)kSecAttrAccessControl] = (__bridge_transfer id)accessControl;
        } else {
#if TARGET_OS_IOS
            NSLog(@"%@", @"Unavailable 'Touch ID integration' on iOS versions prior to 8.0.");
#else
            NSLog(@"%@", @"Unavailable 'Touch ID integration' on OS X versions prior to 10.10.");
#endif
        }
    } else {
        if (floor(NSFoundationVersionNumber) <= floor(iOS_7_1_or_10_9_2) && _accessibility == UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly) {
#if TARGET_OS_IOS
            NSLog(@"%@", @"Unavailable 'UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly' attribute on iOS versions prior to 8.0.");
#else
            NSLog(@"%@", @"Unavailable 'UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly' attribute on OS X versions prior to 10.10.");
#endif
        } else {
            if (accessibilityObject) {
                attributes[(__bridge __strong id)kSecAttrAccessible] = (__bridge id)accessibilityObject;
            }
        }
    }
    
    if (floor(NSFoundationVersionNumber) > floor(993.00)) { // iOS 7+
        attributes[(__bridge __strong id)kSecAttrSynchronizable] = @(_synchronizable);
    }
    
    return attributes;
}

#pragma mark -

- (CFTypeRef)itemClassObject
{
    switch (_itemClass) {
        case UICKeyChainStoreItemClassGenericPassword:
            return kSecClassGenericPassword;
        case UICKeyChainStoreItemClassInternetPassword:
            return kSecClassInternetPassword;
        default:
            return nil;
    }
}

- (CFTypeRef)protocolTypeObject
{
    switch (_protocolType) {
        case UICKeyChainStoreProtocolTypeFTP:
            return kSecAttrProtocolFTP;
        case UICKeyChainStoreProtocolTypeFTPAccount:
            return kSecAttrProtocolFTPAccount;
        case UICKeyChainStoreProtocolTypeHTTP:
            return kSecAttrProtocolHTTP;
        case UICKeyChainStoreProtocolTypeIRC:
            return kSecAttrProtocolIRC;
        case UICKeyChainStoreProtocolTypeNNTP:
            return kSecAttrProtocolNNTP;
        case UICKeyChainStoreProtocolTypePOP3:
            return kSecAttrProtocolPOP3;
        case UICKeyChainStoreProtocolTypeSMTP:
            return kSecAttrProtocolSMTP;
        case UICKeyChainStoreProtocolTypeSOCKS:
            return kSecAttrProtocolSOCKS;
        case UICKeyChainStoreProtocolTypeIMAP:
            return kSecAttrProtocolIMAP;
        case UICKeyChainStoreProtocolTypeLDAP:
            return kSecAttrProtocolLDAP;
        case UICKeyChainStoreProtocolTypeAppleTalk:
            return kSecAttrProtocolAppleTalk;
        case UICKeyChainStoreProtocolTypeAFP:
            return kSecAttrProtocolAFP;
        case UICKeyChainStoreProtocolTypeTelnet:
            return kSecAttrProtocolTelnet;
        case UICKeyChainStoreProtocolTypeSSH:
            return kSecAttrProtocolSSH;
        case UICKeyChainStoreProtocolTypeFTPS:
            return kSecAttrProtocolFTPS;
        case UICKeyChainStoreProtocolTypeHTTPS:
            return kSecAttrProtocolHTTPS;
        case UICKeyChainStoreProtocolTypeHTTPProxy:
            return kSecAttrProtocolHTTPProxy;
        case UICKeyChainStoreProtocolTypeHTTPSProxy:
            return kSecAttrProtocolHTTPSProxy;
        case UICKeyChainStoreProtocolTypeFTPProxy:
            return kSecAttrProtocolFTPProxy;
        case UICKeyChainStoreProtocolTypeSMB:
            return kSecAttrProtocolSMB;
        case UICKeyChainStoreProtocolTypeRTSP:
            return kSecAttrProtocolRTSP;
        case UICKeyChainStoreProtocolTypeRTSPProxy:
            return kSecAttrProtocolRTSPProxy;
        case UICKeyChainStoreProtocolTypeDAAP:
            return kSecAttrProtocolDAAP;
        case UICKeyChainStoreProtocolTypeEPPC:
            return kSecAttrProtocolEPPC;
        case UICKeyChainStoreProtocolTypeNNTPS:
            return kSecAttrProtocolNNTPS;
        case UICKeyChainStoreProtocolTypeLDAPS:
            return kSecAttrProtocolLDAPS;
        case UICKeyChainStoreProtocolTypeTelnetS:
            return kSecAttrProtocolTelnetS;
        case UICKeyChainStoreProtocolTypeIRCS:
            return kSecAttrProtocolIRCS;
        case UICKeyChainStoreProtocolTypePOP3S:
            return kSecAttrProtocolPOP3S;
        default:
            return nil;
    }
}

- (CFTypeRef)authenticationTypeObject
{
    switch (_authenticationType) {
        case UICKeyChainStoreAuthenticationTypeNTLM:
            return kSecAttrAuthenticationTypeNTLM;
        case UICKeyChainStoreAuthenticationTypeMSN:
            return kSecAttrAuthenticationTypeMSN;
        case UICKeyChainStoreAuthenticationTypeDPA:
            return kSecAttrAuthenticationTypeDPA;
        case UICKeyChainStoreAuthenticationTypeRPA:
            return kSecAttrAuthenticationTypeRPA;
        case UICKeyChainStoreAuthenticationTypeHTTPBasic:
            return kSecAttrAuthenticationTypeHTTPBasic;
        case UICKeyChainStoreAuthenticationTypeHTTPDigest:
            return kSecAttrAuthenticationTypeHTTPDigest;
        case UICKeyChainStoreAuthenticationTypeHTMLForm:
            return kSecAttrAuthenticationTypeHTMLForm;
        case UICKeyChainStoreAuthenticationTypeDefault:
            return kSecAttrAuthenticationTypeDefault;
        default:
            return nil;
    }
}

- (CFTypeRef)accessibilityObject
{
    switch (_accessibility) {
        case UICKeyChainStoreAccessibilityWhenUnlocked:
            return kSecAttrAccessibleWhenUnlocked;
        case UICKeyChainStoreAccessibilityAfterFirstUnlock:
            return kSecAttrAccessibleAfterFirstUnlock;
        case UICKeyChainStoreAccessibilityAlways:
            return kSecAttrAccessibleAlways;
        case UICKeyChainStoreAccessibilityWhenPasscodeSetThisDeviceOnly:
            return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly;
        case UICKeyChainStoreAccessibilityWhenUnlockedThisDeviceOnly:
            return kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
        case UICKeyChainStoreAccessibilityAfterFirstUnlockThisDeviceOnly:
            return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
        case UICKeyChainStoreAccessibilityAlwaysThisDeviceOnly:
            return kSecAttrAccessibleAlwaysThisDeviceOnly;
        default:
            return nil;
    }
}

+ (NSError *)argumentError:(NSString *)message
{
    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:UICKeyChainStoreErrorInvalidArguments userInfo:@{NSLocalizedDescriptionKey: message}];
    NSLog(@"error: [%@] %@", @(error.code), error.localizedDescription);
    return error;
}

+ (NSError *)conversionError:(NSString *)message
{
    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:-67594 userInfo:@{NSLocalizedDescriptionKey: message}];
    NSLog(@"error: [%@] %@", @(error.code), error.localizedDescription);
    return error;
}

+ (NSError *)securityError:(OSStatus)status
{
    NSString *message = @"Security error has occurred.";
#if TARGET_OS_MAC && !TARGET_OS_IPHONE
    CFStringRef description = SecCopyErrorMessageString(status, NULL);
    if (description) {
        message = (__bridge_transfer NSString *)description;
    }
#endif
    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey: message}];
    NSLog(@"OSStatus error: [%@] %@", @(error.code), error.localizedDescription);
    return error;
}

+ (NSError *)unexpectedError:(NSString *)message
{
    NSError *error = [NSError errorWithDomain:UICKeyChainStoreErrorDomain code:-99999 userInfo:@{NSLocalizedDescriptionKey: message}];
    NSLog(@"error: [%@] %@", @(error.code), error.localizedDescription);
    return error;
}

@end

@implementation UICKeyChainStore (Deprecation)

- (void)synchronize
{
    // Deprecated, calling this method is no longer required
}

- (BOOL)synchronizeWithError:(NSError *__autoreleasing *)error
{
    // Deprecated, calling this method is no longer required
    return true;
}

@end
