pEpObjCAdapter/PEPSync.m
author buff <andreas@pep-project.org>
Thu, 09 Jul 2020 19:02:58 +0200
branchIOSAD-177
changeset 1681 fa96643a871a
parent 1680 914bafa5e6d3
permissions -rw-r--r--
IOSAD-177 fixes: passphrases from provider not cached
     1 //
     2 //  PEPSync.m
     3 //  pEpObjCAdapter
     4 //
     5 //  Created by Dirk Zimmermann on 04.10.18.
     6 //  Copyright © 2018 p≡p. All rights reserved.
     7 //
     8 
     9 #import <os/log.h>
    10 
    11 #import "pEpEngine.h"
    12 
    13 #import "PEPSync.h"
    14 #import "PEPSync_Internal.h"
    15 
    16 #import "PEPSendMessageDelegate.h"
    17 #import "PEPNotifyHandshakeDelegate.h"
    18 #import "PEPMessageUtil.h"
    19 #import "PEPMessage.h"
    20 #import "PEPQueue.h"
    21 #import "PEPObjCAdapter.h"
    22 #import "NSError+PEP+Internal.h"
    23 #import "PEPSessionProvider.h"
    24 #import "PEPInternalSession.h"
    25 #import "PEPPassphraseCache.h"
    26 
    27 // MARK: - Internals
    28 
    29 static os_log_t s_logger;
    30 
    31 typedef PEP_STATUS (* t_messageToSendCallback)(struct _message * _Nullable msg);
    32 typedef int (* t_injectSyncCallback)(SYNC_EVENT ev, void *management);
    33 
    34 @interface PEPSync ()
    35 
    36 @property (nonatomic, nonnull) PEPQueue *queue;
    37 @property (nonatomic, nullable) NSThread *syncThread;
    38 @property (nonatomic, nullable) NSConditionLock *conditionLockForJoiningSyncThread;
    39 /// Used to block messageToSend() until the client configured a passphrase.
    40 @property (atomic, nullable) dispatch_group_t blockmessageToSendGroup;
    41 /// Object used for synchronizing modifications of `blockmessageToSendGroup`.
    42 @property (atomic, nonnull) NSObject *lockObjectBlockmessageToSendGroupChanges;
    43 /// True if someone called `shutdown()`
    44 @property (atomic) BOOL shutdownRequested;
    45 
    46 /// The session created and used by the sync loop
    47 @property (nonatomic, nullable) PEPInternalSession *syncLoopSession;
    48 
    49 /**
    50  @Return: The callback for message sending that should be used on every session init.
    51  */
    52 + (t_messageToSendCallback)messageToSendCallback;
    53 
    54 /**
    55  @Return: The callback for injectiong sync messages that should be used on every session init.
    56  */
    57 + (t_injectSyncCallback)injectSyncCallback;
    58 
    59 - (PEP_STATUS)messageToSend:(struct _message * _Nullable)msg;
    60 
    61 - (int)injectSyncEvent:(SYNC_EVENT)event isFromShutdown:(BOOL)isFromShutdown;
    62 
    63 - (PEP_STATUS)notifyHandshake:(pEp_identity *)me
    64                       partner:(pEp_identity *)partner
    65                        signal:(sync_handshake_signal)signal;
    66 
    67 - (SYNC_EVENT)retrieveNextSyncEvent:(time_t)threshold;
    68 
    69 @end
    70 
    71 // MARK: - Callbacks called by the engine, used in session init
    72 
    73 static PEP_STATUS s_messageToSendObjc(struct _message * _Nullable msg)
    74 {
    75     PEPSync *pEpSync = [PEPSync sharedInstance];
    76 
    77     if (pEpSync) {
    78         return [pEpSync messageToSend:msg];
    79     } else {
    80         return PEP_SYNC_NO_NOTIFY_CALLBACK;
    81     }
    82 }
    83 
    84 static int s_inject_sync_event(SYNC_EVENT ev, void *management)
    85 {
    86     PEPSync *pEpSync = [PEPSync sharedInstance];
    87 
    88     if (pEpSync) {
    89         // The inject comes from the engine, so we know it's not the
    90         // adapter client calling shutdown.
    91         return [pEpSync injectSyncEvent:ev isFromShutdown:NO];
    92     } else {
    93         return 1;
    94     }
    95 }
    96 
    97 // MARK: - Callbacks called by the engine, used in register_sync_callbacks
    98 
    99 static PEP_STATUS s_notifyHandshake(pEp_identity *me,
   100                                     pEp_identity *partner,
   101                                     sync_handshake_signal signal)
   102 {
   103     PEPSync *pEpSync = [PEPSync sharedInstance];
   104 
   105     if (pEpSync) {
   106         return [pEpSync notifyHandshake:me partner:partner signal:signal];
   107     } else {
   108         return PEP_SYNC_NO_NOTIFY_CALLBACK;
   109     }
   110 }
   111 
   112 static SYNC_EVENT s_retrieve_next_sync_event(void *management, unsigned threshold)
   113 {
   114     PEPSync *sync = [PEPSync sharedInstance];
   115     return [sync retrieveNextSyncEvent:threshold];
   116 }
   117 
   118 // MARK: - Internal globals
   119 
   120 static __weak PEPSync *s_pEpSync;
   121 
   122 // MARK: - Public PEPSync class
   123 
   124 @implementation PEPSync
   125 
   126 + (t_messageToSendCallback)messageToSendCallback
   127 {
   128     return s_messageToSendObjc;
   129 }
   130 
   131 + (t_injectSyncCallback)injectSyncCallback
   132 {
   133     return s_inject_sync_event;
   134 }
   135 
   136 + (PEP_SESSION)createSession:(NSError **)error
   137 {
   138     PEP_SESSION session = NULL;
   139 
   140     PEP_STATUS status = init(&session,
   141                              [PEPSync messageToSendCallback],
   142                              [PEPSync injectSyncCallback]);
   143 
   144     if (status != PEP_STATUS_OK) {
   145         if (error) {
   146             *error = [NSError errorWithPEPStatusInternal:status];
   147             os_log(s_logger, "error creating session: %{public}@", *error);
   148         }
   149         return nil;
   150     }
   151 
   152     return session;
   153 }
   154 
   155 - (instancetype)initWithSendMessageDelegate:(id<PEPSendMessageDelegate>
   156                                              _Nullable)sendMessageDelegate
   157                     notifyHandshakeDelegate:(id<PEPNotifyHandshakeDelegate>
   158                                              _Nullable)notifyHandshakeDelegate
   159 {
   160     if (self = [super init]) {
   161         _sendMessageDelegate = sendMessageDelegate;
   162         _notifyHandshakeDelegate = notifyHandshakeDelegate;
   163         _queue = [PEPQueue new];
   164         s_pEpSync = self;
   165         _lockObjectBlockmessageToSendGroupChanges = [NSObject new];
   166         _shutdownRequested = NO;
   167     }
   168     return self;
   169 }
   170 
   171 - (void)startup
   172 {
   173     self.shutdownRequested = NO;
   174     [self stopWaiting];
   175 
   176     if (self.syncThread != nil) {
   177         // already started
   178         return;
   179     }
   180 
   181     NSThread *theSyncThread = [[NSThread alloc] initWithTarget:self
   182                                                       selector:@selector(syncThreadLoop:)
   183                                                         object:nil];
   184     theSyncThread.name = @"pEp-sync-loop";
   185     self.syncThread = theSyncThread;
   186 
   187     // Make sure queue is empty when we start.
   188     [self.queue removeAllObjects];
   189 
   190     [self assureMainSessionExists]; //???: Why do we need that? Afaics syncThreadLoop gets the session from PEPSessionProvider, which should have taken care of main session existance.
   191 
   192     self.conditionLockForJoiningSyncThread = [[NSConditionLock alloc] initWithCondition:NO];
   193     [theSyncThread start];
   194 }
   195 
   196 - (void)shutdown
   197 {
   198     self.shutdownRequested = YES;
   199     [self stopWaiting];
   200 
   201     if (self.syncThread) {
   202         [self injectSyncEvent:nil isFromShutdown:YES];
   203     }
   204 }
   205 
   206 - (void)handleNewPassphraseConfigured {
   207     [self stopWaiting];
   208 }
   209 
   210 // MARK: - Private
   211 
   212 + (void)initialize
   213 {
   214     s_logger = os_log_create("security.pEp.adapter", "PEPSync");
   215 }
   216 
   217 + (PEPSync * _Nullable)sharedInstance //!!!: is not private but internal
   218 {
   219     return s_pEpSync;
   220 }
   221 
   222 - (void)assureMainSessionExists
   223 {
   224     PEPInternalSession *session __attribute__((unused)) = [PEPSessionProvider session];
   225 }
   226 
   227 - (void)syncThreadLoop:(id)object
   228 {
   229     [self.conditionLockForJoiningSyncThread lock];
   230 
   231     os_log(s_logger, "trying to start the sync loop");
   232 
   233     self.syncLoopSession = [PEPSessionProvider session];
   234 
   235     if (self.syncLoopSession) {
   236         PEP_STATUS status = register_sync_callbacks(self.syncLoopSession.session,
   237                                                     nil,
   238                                                     s_notifyHandshake,
   239                                                     s_retrieve_next_sync_event);
   240         if (status == PEP_STATUS_OK) {
   241             status = do_sync_protocol(self.syncLoopSession.session, nil);
   242             if (status != PEP_STATUS_OK) {
   243                 os_log_error(s_logger, "do_sync_protocol returned PEP_STATUS %d", status);
   244                 os_log(s_logger, "sync loop is NOT running");
   245             }
   246             unregister_sync_callbacks(self.syncLoopSession.session);
   247         } else {
   248             os_log_error(s_logger, "register_sync_callbacks returned PEP_STATUS %d", status);
   249             os_log(s_logger, "sync loop is NOT running");
   250         }
   251     } else {
   252         os_log_error(s_logger, "could not create session for starting the sync loop");
   253     }
   254 
   255     os_log(s_logger, "sync loop finished");
   256 
   257     self.syncLoopSession = nil;
   258     self.syncThread = nil;
   259 
   260     [self.conditionLockForJoiningSyncThread unlockWithCondition:YES];
   261 }
   262 
   263 - (PEP_STATUS)messageToSend:(struct _message * _Nullable)msg
   264 {
   265     [self blockUntilPassphraseIsEnteredIfRequired];
   266     if (self.shutdownRequested) {
   267         // The client has signalled that she was unable to provide a passphrase by calling
   268         // `shutdown()`.
   269         // We signal the same to the Engine.
   270         return PEP_SYNC_NO_CHANNEL;
   271     }
   272 
   273     if (msg == NULL && [NSThread currentThread] == self.syncThread) {
   274         static NSMutableArray *passphrasesCopy = nil;
   275         static BOOL makeNewCopy = YES;
   276 
   277         if (makeNewCopy) {
   278             passphrasesCopy = [NSMutableArray
   279                                arrayWithArray:[self.syncLoopSession.passphraseCache passphrases]];
   280 
   281             if (self.syncLoopSession.passphraseCache.storedPassphrase) {
   282                 [passphrasesCopy
   283                  insertObject:self.syncLoopSession.passphraseCache.storedPassphrase
   284                  atIndex:0];
   285             }
   286 
   287             if ([passphrasesCopy count] == 0) {
   288                 makeNewCopy = YES;
   289                 [self nextCallMustWait];
   290                 return PEP_PASSPHRASE_REQUIRED;
   291             } else {
   292                 makeNewCopy = NO;
   293             }
   294         }
   295 
   296         if ([passphrasesCopy count] == 0) {
   297             makeNewCopy = YES;
   298             [self nextCallMustWait];
   299             return PEP_WRONG_PASSPHRASE;
   300         } else {
   301             NSString *password = [passphrasesCopy firstObject];
   302             [passphrasesCopy removeObjectAtIndex:0];
   303             [self.syncLoopSession configurePassphrase:password error:nil];
   304             return PEP_STATUS_OK;
   305         }
   306     } else if (msg != NULL) {
   307         if (self.sendMessageDelegate) {
   308             PEPMessage *theMessage = pEpMessageFromStruct(msg);
   309             return (PEP_STATUS) [self.sendMessageDelegate sendMessage:theMessage];
   310         } else {
   311             return PEP_SYNC_NO_MESSAGE_SEND_CALLBACK;
   312         }
   313     } else {
   314         return PEP_SYNC_ILLEGAL_MESSAGE;
   315     }
   316 }
   317 
   318 /// Injects the given event into the queue.
   319 /// @param event The event to inject, which may contain a nil value, which means the
   320 ///  sync loop should stop.
   321 /// @param isFromShutdown This is `YES` when coming from `shutdown` itself, and `NO`
   322 ///  otherwise (e.g., when the engine requests a shutdown by injecting a nil event.
   323 - (int)injectSyncEvent:(SYNC_EVENT)event isFromShutdown:(BOOL)isFromShutdown
   324 {
   325     NSValue *value = [NSValue valueWithBytes:&event objCType:@encode(SYNC_EVENT)];
   326 
   327     if (event) {
   328         [self.queue enqueue:value];
   329     } else {
   330         // This is a nil event, which means shut it all down.
   331         if ([NSThread currentThread] != self.syncThread) {
   332             // Only do this when the shutdown is not coming in on the sync thread.
   333             // Otherwise it will just exit out of the sync loop and be done.
   334             [self.queue prequeue:value];
   335             [self.conditionLockForJoiningSyncThread lockWhenCondition:YES];
   336             [self.conditionLockForJoiningSyncThread unlock];
   337             self.conditionLockForJoiningSyncThread = nil;
   338         }
   339         if (!isFromShutdown) {
   340             // Only inform the delegate if the shutdown came from the engine
   341             [self.notifyHandshakeDelegate engineShutdownKeySync];
   342         }
   343     }
   344 
   345     return 0;
   346 }
   347 
   348 - (PEP_STATUS)notifyHandshake:(pEp_identity *)me
   349                       partner:(pEp_identity *)partner
   350                        signal:(sync_handshake_signal)signal
   351 {
   352     if (self.notifyHandshakeDelegate) {
   353         PEPIdentity *meIdentity = PEP_identityFromStruct(me);
   354         PEPIdentity *partnerIdentity = partner != nil ? PEP_identityFromStruct(partner) : nil;
   355         return (PEP_STATUS) [self.notifyHandshakeDelegate
   356                              notifyHandshake:NULL
   357                              me:meIdentity
   358                              partner:partnerIdentity
   359                              signal:(PEPSyncHandshakeSignal) signal];
   360     } else {
   361         return PEP_SYNC_NO_NOTIFY_CALLBACK;
   362     }
   363 }
   364 
   365 - (SYNC_EVENT)retrieveNextSyncEvent:(time_t)threshold
   366 {
   367     NSValue *value = [self.queue timedDequeue:&threshold];
   368     if (value) {
   369         SYNC_EVENT event;
   370         [value getValue:&event];
   371         return event;
   372     } else {
   373         return new_sync_timeout_event();
   374     }
   375 }
   376 
   377 // MARK: Blocking (messageToSend)
   378 
   379 - (void)blockUntilPassphraseIsEnteredIfRequired {
   380     if (self.blockmessageToSendGroup) {
   381         dispatch_group_wait(self.blockmessageToSendGroup, DISPATCH_TIME_FOREVER);
   382     }
   383 }
   384 
   385 - (void)nextCallMustWait {
   386     @synchronized (self.blockmessageToSendGroup) {
   387         if (!self.blockmessageToSendGroup) {
   388             self.blockmessageToSendGroup = dispatch_group_create();
   389         }
   390         dispatch_group_enter(self.blockmessageToSendGroup);
   391     }
   392 }
   393 
   394 - (void)stopWaiting {
   395     @synchronized (self.blockmessageToSendGroup) {
   396         if (self.blockmessageToSendGroup) {
   397             dispatch_group_leave(self.blockmessageToSendGroup);
   398             self.blockmessageToSendGroup = nil;
   399         }
   400     }
   401 }
   402 
   403 @end