]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTCuttlefishAccountStateHolder.m
Security-59306.101.1.tar.gz
[apple/security.git] / keychain / ot / OTCuttlefishAccountStateHolder.m
1
2 #import "keychain/ot/OTCuttlefishAccountStateHolder.h"
3
4 #import "keychain/categories/NSError+UsefulConstructors.h"
5 #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h"
6 #import "keychain/ot/ObjCImprovements.h"
7
8 @interface OTCuttlefishAccountStateHolder ()
9 @property dispatch_queue_t queue;
10 @property dispatch_queue_t notifyQueue;
11
12 @property NSString* containerName;
13 @property NSString* contextID;
14
15 @property NSMutableSet<id<OTCuttlefishAccountStateHolderNotifier>>* monitors;
16 @end
17
18 @implementation OTCuttlefishAccountStateHolder
19
20 - (instancetype)initWithQueue:(dispatch_queue_t)queue
21 container:(NSString*)containerName
22 context:(NSString*)contextID
23 {
24 if((self = [super init])) {
25 _queue = queue;
26 _notifyQueue = dispatch_queue_create("OTCuttlefishAccountStateHolderNotifier", NULL);
27 _containerName = containerName;
28 _contextID = contextID;
29 _monitors = [NSMutableSet set];
30 }
31 return self;
32 }
33
34 - (void)registerNotification:(id<OTCuttlefishAccountStateHolderNotifier>)notifier
35 {
36 [self.monitors addObject:notifier];
37 }
38
39 - (OTAccountMetadataClassC* _Nullable)loadOrCreateAccountMetadata:(NSError**)error
40 {
41 __block OTAccountMetadataClassC* metadata = nil;
42 __block NSError* localError = nil;
43 dispatch_sync(self.queue, ^{
44 metadata = [self _onqueueLoadOrCreateAccountMetadata:&localError];
45 });
46
47 if(error && localError) {
48 *error = localError;
49 }
50 return metadata;
51 }
52
53 - (OTAccountMetadataClassC* _Nullable)_onqueueLoadOrCreateAccountMetadata:(NSError**)error
54 {
55 dispatch_assert_queue(self.queue);
56
57 NSError* localError = nil;
58 OTAccountMetadataClassC* current = [OTAccountMetadataClassC loadFromKeychainForContainer:self.containerName contextID:self.contextID error:&localError];
59
60 if(!current || localError) {
61 if([localError.domain isEqualToString:NSOSStatusErrorDomain] && localError.code == errSecItemNotFound) {
62 // That's okay, this is the first time we're saving this.
63 current = [[OTAccountMetadataClassC alloc] init];
64 current.attemptedJoin = OTAccountMetadataClassC_AttemptedAJoinState_NOTATTEMPTED;
65 } else {
66 // No good.
67 if(error) {
68 *error = localError;
69 }
70 return nil;
71 }
72 }
73
74 return current;
75 }
76
77 - (NSString * _Nullable)getEgoPeerID:(NSError * _Nullable *)error {
78 NSError* localError = nil;
79
80 OTAccountMetadataClassC* current = [self loadOrCreateAccountMetadata:&localError];
81
82 if(localError || !current) {
83 if(error) {
84 *error = localError;
85 }
86 return nil;
87 }
88
89 if(!current.peerID) {
90 if(error) {
91 *error = [NSError errorWithDomain:OTCuttlefishContextErrorDomain
92 code:OTCCNoExistingPeerID
93 description:@"No existing ego peer ID"];
94 }
95
96 return nil;
97 }
98
99 return current.peerID;
100 }
101
102 - (OTAccountMetadataClassC_AttemptedAJoinState)fetchPersistedJoinAttempt:(NSError * _Nullable *)error {
103 NSError* localError = nil;
104 OTAccountMetadataClassC* current = [self loadOrCreateAccountMetadata:&localError];
105
106 if(localError || !current) {
107 if(error) {
108 *error = localError;
109 }
110 return OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN;
111 }
112 return current.attemptedJoin;
113 }
114
115 - (NSDate *)lastHealthCheckupDate:(NSError * _Nullable *)error {
116 NSError* localError = nil;
117
118 OTAccountMetadataClassC* current = [self loadOrCreateAccountMetadata:&localError];
119
120 if(localError || !current) {
121 if(error) {
122 *error = localError;
123 }
124 return NULL;
125 }
126
127 return [NSDate dateWithTimeIntervalSince1970: ((NSTimeInterval)current.lastHealthCheckup) / 1000.0];
128 }
129
130
131 - (BOOL)persistNewEgoPeerID:(NSString*)peerID error:(NSError**)error {
132 return [self persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
133 metadata.peerID = peerID;
134 return metadata;
135 } error:error];
136 }
137
138 - (BOOL)persistNewTrustState:(OTAccountMetadataClassC_TrustState)newState
139 error:(NSError**)error
140 {
141 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
142 metadata.trustState = newState;
143 return metadata;
144 } error:error];
145 }
146
147 - (BOOL)persistNewAccountState:(OTAccountMetadataClassC_AccountState)newState
148 optionalAltDSID:(NSString* _Nullable)altDSID
149 error:(NSError**)error
150 {
151 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
152 metadata.icloudAccountState = newState;
153 metadata.altDSID = altDSID;
154 return metadata;
155 } error:error];
156 }
157
158 - (BOOL)persistNewEpoch:(uint64_t)epoch
159 error:(NSError**)error
160 {
161 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
162 metadata.epoch = epoch;
163 return metadata;
164 } error:error];
165 }
166
167 - (BOOL)persistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC*))makeChanges
168 error:(NSError**)error
169 {
170 __block NSError* localError = nil;
171 __block OTAccountMetadataClassC* newState = nil;
172 __block OTAccountMetadataClassC* oldState = nil;
173
174 dispatch_sync(self.queue, ^void {
175 oldState = [self _onqueueLoadOrCreateAccountMetadata:&localError];
176 if(!oldState) {
177 return;
178 }
179
180 newState = makeChanges([oldState copy]);
181 if(newState && ![newState saveToKeychainForContainer:self.containerName contextID:self.contextID error:&localError]) {
182 newState = nil;
183 }
184 });
185
186 if(localError && error) {
187 *error = localError;
188 }
189
190 if (newState) {
191 [self asyncNotifyAccountStateChanges:newState from:oldState];
192 return YES;
193 } else {
194 return NO;
195 }
196 }
197
198 - (BOOL)persistLastHealthCheck:(NSDate*)lastCheck
199 error:(NSError**)error
200 {
201 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
202 metadata.lastHealthCheckup = (uint64_t) ([lastCheck timeIntervalSince1970] * 1000);
203 return metadata;
204 } error:error];
205 }
206
207 - (BOOL)persistOctagonJoinAttempt:(OTAccountMetadataClassC_AttemptedAJoinState)attempt
208 error:(NSError**)error
209 {
210 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
211 metadata.attemptedJoin = attempt;
212 return metadata;
213 } error:error];
214 }
215
216 - (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC* metadata))makeChanges
217 error:(NSError**)error
218 {
219 __block NSError* localError = nil;
220 dispatch_assert_queue(self.queue);
221
222 OTAccountMetadataClassC *oldState = nil;
223 OTAccountMetadataClassC *newState = nil;
224
225 oldState = [self _onqueueLoadOrCreateAccountMetadata:&localError];
226 if(oldState) {
227 newState = makeChanges([oldState copy]);
228
229 if(![newState saveToKeychainForContainer:self.containerName contextID:self.contextID error:&localError]) {
230 newState = nil;
231 }
232 }
233
234 if (error && localError) {
235 *error = localError;
236 }
237
238 if (newState) {
239 [self asyncNotifyAccountStateChanges:newState from:oldState];
240 return YES;
241 } else {
242 return NO;
243 }
244 }
245
246 - (void)asyncNotifyAccountStateChanges:(OTAccountMetadataClassC *)newState from:(OTAccountMetadataClassC *)oldState
247 {
248 WEAKIFY(self);
249
250 dispatch_async(self.notifyQueue, ^{
251 STRONGIFY(self);
252
253 for (id<OTCuttlefishAccountStateHolderNotifier> monitor in self.monitors) {
254 [monitor accountStateUpdated:newState from:oldState];
255 }
256 });
257 }
258
259
260 @end