pEpObjCAdapter/PEPPassphraseCache.m
author Dirk Zimmermann <dz@pep.security>
Mon, 29 Jun 2020 16:39:43 +0200
branchIOSAD-172
changeset 1547 516474ecf260
parent 1541 c6f4eab1ed95
child 1548 bd20f1a6c3cc
permissions -rw-r--r--
IOSAD-172 Less sorting
     1 //
     2 //  PEPPassphraseCache.m
     3 //  pEpObjCAdapter
     4 //
     5 //  Created by Dirk Zimmermann on 25.06.20.
     6 //  Copyright © 2020 p≡p. All rights reserved.
     7 //
     8 
     9 #import "PEPPassphraseCache.h"
    10 
    11 #import "PEPPassphraseCacheInternal.h"
    12 
    13 #import "PEPPassphraseCacheEntry.h"
    14 
    15 static NSUInteger s_maxNumberOfPassphrases = 20;
    16 static NSTimeInterval s_defaultTimeoutInSeconds = 10 * 60;
    17 static NSTimeInterval s_defaultCheckExpiryInterval = 60;
    18 
    19 @interface PEPPassphraseCache ()
    20 
    21 /// Timeout of passwords in seconds.
    22 @property (nonatomic) NSTimeInterval timeout;
    23 
    24 @property (nonatomic) dispatch_queue_t queue;
    25 @property (nonatomic) NSMutableArray<PEPPassphraseCacheEntry *> *mutablePassphraseEntries;
    26 @property (nonatomic) dispatch_source_t timer;
    27 
    28 @end
    29 
    30 @implementation PEPPassphraseCache
    31 
    32 static PEPPassphraseCache *s_sharedInstance;
    33 
    34 + (void)initialize
    35 {
    36     static BOOL initialized = NO;
    37     if (!initialized) {
    38         initialized = YES;
    39         s_sharedInstance = [[PEPPassphraseCache alloc] init];
    40     }
    41 }
    42 
    43 + (instancetype)sharedInstance
    44 {
    45     return s_sharedInstance;
    46 }
    47 
    48 /// Internal constructor (for now).
    49 - (instancetype)initWithPassphraseTimeout:(NSTimeInterval)timeout
    50                       checkExpiryInterval:(NSTimeInterval)checkExpiryInterval
    51 {
    52     self = [super init];
    53     if (self) {
    54         _timeout = timeout;
    55         _queue = dispatch_queue_create("PEPPassphraseCache Queue", DISPATCH_QUEUE_SERIAL);
    56         _mutablePassphraseEntries = [NSMutableArray arrayWithCapacity:s_maxNumberOfPassphrases];
    57 
    58         // we have a strong reference to the timer, but the timer doesn't have one to us
    59         typeof(self) __weak weakSelf = self;
    60 
    61         _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue);
    62         dispatch_source_set_timer(_timer,
    63                                   DISPATCH_TIME_NOW,
    64                                   checkExpiryInterval * NSEC_PER_SEC,
    65                                   checkExpiryInterval / 10 * NSEC_PER_SEC);
    66         dispatch_source_set_event_handler(_timer, ^{
    67             [weakSelf removeStaleEntries];
    68         });
    69         dispatch_resume(_timer);
    70     }
    71     return self;
    72 }
    73 
    74 /// Public constructor with default values.
    75 - (instancetype)init
    76 {
    77     return [self initWithPassphraseTimeout:s_defaultTimeoutInSeconds
    78                        checkExpiryInterval:s_defaultCheckExpiryInterval];
    79 }
    80 
    81 - (void)addPassphrase:(NSString *)passphrase
    82 {
    83     PEPPassphraseCacheEntry *entry = [[PEPPassphraseCacheEntry alloc]
    84                                       initWithPassphrase:passphrase];
    85     dispatch_sync(self.queue, ^{
    86         [self.mutablePassphraseEntries insertObject:entry atIndex:0];
    87         if (self.mutablePassphraseEntries.count > s_maxNumberOfPassphrases) {
    88             [self.mutablePassphraseEntries removeLastObject];
    89         }
    90     });
    91 }
    92 
    93 - (NSArray<NSString *> *)passphrases
    94 {
    95     NSMutableArray *resultingPassphrases = [NSMutableArray
    96                                             arrayWithCapacity:s_maxNumberOfPassphrases + 1];
    97     dispatch_sync(self.queue, ^{
    98         for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
    99             if (![self isExpiredPassphraseEntry:entry]) {
   100                 [resultingPassphrases addObject:entry.passphrase];
   101             }
   102         }
   103     });
   104     [resultingPassphrases insertObject:@"" atIndex:0];
   105     return [NSArray arrayWithArray:resultingPassphrases];
   106 }
   107 
   108 /// Remove password entries that have timed out.
   109 /// - Note: Assumes it gets called on `queue`.
   110 - (void)removeStaleEntries
   111 {
   112     NSMutableArray *resultingPassphrases = [NSMutableArray
   113                                             arrayWithCapacity:s_maxNumberOfPassphrases];
   114 
   115     for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   116         if (![self isExpiredPassphraseEntry:entry]) {
   117             [resultingPassphrases addObject:entry];
   118         }
   119     }
   120 
   121     [self.mutablePassphraseEntries removeAllObjects];
   122     [self.mutablePassphraseEntries addObjectsFromArray:resultingPassphrases];
   123 }
   124 
   125 - (void)resetTimeoutForPassphrase:(NSString *)passphrase
   126 {
   127     if ([passphrase isEqualToString:@""]) {
   128         // Ignore the empty passphrase, it's always there from the client's view,
   129         // but not contained in the internal model.
   130         return;
   131     }
   132 
   133     dispatch_sync(self.queue, ^{
   134         BOOL foundAtLeastOnce = NO;
   135         for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   136             if ([entry.passphrase isEqualToString:passphrase]) {
   137                 foundAtLeastOnce = YES;
   138                 entry.dateAdded = [NSDate date];
   139             }
   140         }
   141 
   142         if (foundAtLeastOnce) {
   143             [self sortPassphrases];
   144         }
   145     });
   146 }
   147 
   148 - (BOOL)isExpiredPassphraseEntry:(PEPPassphraseCacheEntry *)passphraseEntry
   149 {
   150     NSDate *now = [NSDate date];
   151     NSDate *minimum = [now dateByAddingTimeInterval:-self.timeout];
   152     NSTimeInterval minimumTimeInterval = [minimum timeIntervalSinceReferenceDate];
   153 
   154     if ([passphraseEntry.dateAdded timeIntervalSinceReferenceDate] < minimumTimeInterval) {
   155         return YES;
   156     }
   157 
   158     return NO;
   159 }
   160 
   161 /// Sort the stored passphrases, last (successfully) used or added first.
   162 /// Assumes being called from the internal queue.
   163 - (void)sortPassphrases
   164 {
   165     NSArray *sorted = [self sortedArrayByDateNewestFirst:self.mutablePassphraseEntries];
   166     [self.mutablePassphraseEntries
   167      replaceObjectsInRange:NSMakeRange(0, [self.mutablePassphraseEntries count])
   168      withObjectsFromArray:sorted];
   169 }
   170 
   171 - (NSArray<PEPPassphraseCacheEntry *> *)sortedArrayByDateNewestFirst:(NSArray<PEPPassphraseCacheEntry *> *)array
   172 {
   173     return [array sortedArrayUsingComparator:^NSComparisonResult(PEPPassphraseCacheEntry *entry1,
   174                                                                  PEPPassphraseCacheEntry *entry2) {
   175         NSTimeInterval interval1 = [entry1.dateAdded timeIntervalSinceReferenceDate];
   176         NSTimeInterval interval2 = [entry2.dateAdded timeIntervalSinceReferenceDate];
   177 
   178         if (interval1 > interval2) {
   179             return NSOrderedAscending;
   180         } else if (interval1 < interval2) {
   181             return NSOrderedDescending;
   182         } else {
   183             return NSOrderedSame;
   184         }
   185     }];
   186 }
   187 
   188 @end