2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #import "keychain/ckks/CKKSAPSReceiver.h"
27 #import "keychain/ckks/CKKSCondition.h"
28 #import <CloudKit/CloudKit_Private.h>
29 #include <utilities/debugging.h>
31 @interface CKKSAPSReceiver()
32 // If we receive >0 notifications for a CKRecordZoneID that hasn't been registered yet, give them a fake update when they register
33 @property NSMutableSet<CKRecordZoneID*>* undeliveredUpdates;
36 @implementation CKKSAPSReceiver
38 + (instancetype)receiverForEnvironment:(NSString *)environmentName
39 namedDelegatePort:(NSString*)namedDelegatePort
40 apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass {
41 static NSMutableDictionary<NSString*, CKKSAPSReceiver*>* environmentMap = nil;
43 if(environmentName == nil) {
44 secnotice("ckkspush", "No push environment; not bringing up APS.");
48 @synchronized([self class]) {
49 if(environmentMap == nil) {
50 environmentMap = [[NSMutableDictionary alloc] init];
53 CKKSAPSReceiver* recv = [environmentMap valueForKey: environmentName];
56 recv = [[CKKSAPSReceiver alloc] initWithEnvironmentName: environmentName namedDelegatePort:namedDelegatePort apsConnectionClass: apsConnectionClass];
57 [environmentMap setValue: recv forKey: environmentName];
64 + (dispatch_queue_t)apsDeliveryQueue {
65 static dispatch_queue_t aps_dispatch_queue;
66 static dispatch_once_t onceToken;
67 dispatch_once(&onceToken, ^{
68 aps_dispatch_queue = dispatch_queue_create("aps-callback-queue", DISPATCH_QUEUE_SERIAL);
70 return aps_dispatch_queue;
73 - (instancetype)initWithEnvironmentName:(NSString*)environmentName
74 namedDelegatePort:(NSString*)namedDelegatePort
75 apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass {
76 if(self = [super init]) {
77 _apsConnectionClass = apsConnectionClass;
78 _apsConnection = NULL;
80 _undeliveredUpdates = [[NSMutableSet alloc] init];
82 // APS might be slow. This doesn't need to happen immediately, so let it happen later.
83 __weak __typeof(self) weakSelf = self;
84 dispatch_async([CKKSAPSReceiver apsDeliveryQueue], ^{
85 __strong __typeof(self) strongSelf = weakSelf;
89 strongSelf.apsConnection = [[strongSelf.apsConnectionClass alloc] initWithEnvironmentName:environmentName namedDelegatePort:namedDelegatePort queue:[CKKSAPSReceiver apsDeliveryQueue]];
90 strongSelf.apsConnection.delegate = strongSelf;
92 // The following string should match: [[NSBundle mainBundle] bundleIdentifier]
93 NSString* topic = [kCKPushTopicPrefix stringByAppendingString:@"com.apple.securityd"];
94 [strongSelf.apsConnection setEnabledTopics:@[topic]];
97 _zoneMap = [NSMapTable strongToWeakObjectsMapTable];
102 - (CKKSCondition*)register:(id<CKKSZoneUpdateReceiver>)receiver forZoneID:(CKRecordZoneID *)zoneID {
103 CKKSCondition* finished = [[CKKSCondition alloc] init];
105 __weak __typeof(self) weakSelf = self;
106 dispatch_async([CKKSAPSReceiver apsDeliveryQueue], ^{
107 __strong __typeof(self) strongSelf = weakSelf;
109 secerror("ckks: received registration for released CKKSAPSReceiver");
113 [self.zoneMap setObject:receiver forKey: zoneID];
114 if([strongSelf.undeliveredUpdates containsObject:zoneID]) {
115 [strongSelf.undeliveredUpdates removeObject:zoneID];
117 // Now, send the receiver its fake notification!
118 secerror("ckks: sending fake push to newly-registered zone(%@): %@", zoneID, receiver);
119 [receiver notifyZoneChange:nil];
128 #pragma mark - APS Delegate callbacks
130 - (void)connection:(APSConnection *)connection didReceivePublicToken:(NSData *)publicToken {
132 secnotice("ckkspush", "CKKSAPSDelegate initiated: %@", connection);
135 - (void)connection:(APSConnection *)connection didReceiveToken:(NSData *)token forTopic:(NSString *)topic identifier:(NSString *)identifier {
136 secnotice("ckkspush", "Received per-topic push token \"%@\" for topic \"%@\" identifier \"%@\" on connection %@", token, topic, identifier, connection);
139 - (void)connection:(APSConnection *)connection didReceiveIncomingMessage:(APSIncomingMessage *)message {
140 secnotice("ckkspush", "CKKSAPSDelegate received a message: %@ on connection %@", message, connection);
142 CKNotification* notification = [CKNotification notificationFromRemoteNotificationDictionary:message.userInfo];
144 if(notification.notificationType == CKNotificationTypeRecordZone) {
145 CKRecordZoneNotification* rznotification = (CKRecordZoneNotification*) notification;
147 // Find receiever in map
148 id<CKKSZoneUpdateReceiver> recv = [self.zoneMap objectForKey:rznotification.recordZoneID];
150 [recv notifyZoneChange:rznotification];
152 secerror("ckks: received push for unregistered zone: %@", rznotification);
153 if(rznotification.recordZoneID) {
154 [self.undeliveredUpdates addObject: rznotification.recordZoneID];
158 secerror("ckks: unexpected notification: %@", notification);