2 #import "keychain/ot/OTCuttlefishAccountStateHolder.h"
4 #import "keychain/categories/NSError+UsefulConstructors.h"
5 #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h"
6 #import "keychain/ot/ObjCImprovements.h"
8 @interface OTCuttlefishAccountStateHolder ()
9 @property dispatch_queue_t queue;
10 @property dispatch_queue_t notifyQueue;
12 @property NSString* containerName;
13 @property NSString* contextID;
15 @property NSMutableSet<id<OTCuttlefishAccountStateHolderNotifier>>* monitors;
18 @implementation OTCuttlefishAccountStateHolder
20 - (instancetype)initWithQueue:(dispatch_queue_t)queue
21 container:(NSString*)containerName
22 context:(NSString*)contextID
24 if((self = [super init])) {
26 _notifyQueue = dispatch_queue_create("OTCuttlefishAccountStateHolderNotifier", NULL);
27 _containerName = containerName;
28 _contextID = contextID;
29 _monitors = [NSMutableSet set];
34 - (void)registerNotification:(id<OTCuttlefishAccountStateHolderNotifier>)notifier
36 [self.monitors addObject:notifier];
39 - (OTAccountMetadataClassC* _Nullable)loadOrCreateAccountMetadata:(NSError**)error
41 __block OTAccountMetadataClassC* metadata = nil;
42 __block NSError* localError = nil;
43 dispatch_sync(self.queue, ^{
44 metadata = [self _onqueueLoadOrCreateAccountMetadata:&localError];
47 if(error && localError) {
53 - (OTAccountMetadataClassC* _Nullable)_onqueueLoadOrCreateAccountMetadata:(NSError**)error
55 dispatch_assert_queue(self.queue);
57 NSError* localError = nil;
58 OTAccountMetadataClassC* current = [OTAccountMetadataClassC loadFromKeychainForContainer:self.containerName contextID:self.contextID error:&localError];
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;
77 - (NSString * _Nullable)getEgoPeerID:(NSError * _Nullable *)error {
78 NSError* localError = nil;
80 OTAccountMetadataClassC* current = [self loadOrCreateAccountMetadata:&localError];
82 if(localError || !current) {
91 *error = [NSError errorWithDomain:OTCuttlefishContextErrorDomain
92 code:OTCCNoExistingPeerID
93 description:@"No existing ego peer ID"];
99 return current.peerID;
102 - (OTAccountMetadataClassC_AttemptedAJoinState)fetchPersistedJoinAttempt:(NSError * _Nullable *)error {
103 NSError* localError = nil;
104 OTAccountMetadataClassC* current = [self loadOrCreateAccountMetadata:&localError];
106 if(localError || !current) {
110 return OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN;
112 return current.attemptedJoin;
115 - (NSDate *)lastHealthCheckupDate:(NSError * _Nullable *)error {
116 NSError* localError = nil;
118 OTAccountMetadataClassC* current = [self loadOrCreateAccountMetadata:&localError];
120 if(localError || !current) {
127 return [NSDate dateWithTimeIntervalSince1970: ((NSTimeInterval)current.lastHealthCheckup) / 1000.0];
131 - (BOOL)persistNewEgoPeerID:(NSString*)peerID error:(NSError**)error {
132 return [self persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
133 metadata.peerID = peerID;
138 - (BOOL)persistNewTrustState:(OTAccountMetadataClassC_TrustState)newState
139 error:(NSError**)error
141 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
142 metadata.trustState = newState;
147 - (BOOL)persistNewAccountState:(OTAccountMetadataClassC_AccountState)newState
148 optionalAltDSID:(NSString* _Nullable)altDSID
149 error:(NSError**)error
151 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
152 metadata.icloudAccountState = newState;
153 metadata.altDSID = altDSID;
158 - (BOOL)persistNewEpoch:(uint64_t)epoch
159 error:(NSError**)error
161 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
162 metadata.epoch = epoch;
167 - (BOOL)persistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC*))makeChanges
168 error:(NSError**)error
170 __block NSError* localError = nil;
171 __block OTAccountMetadataClassC* newState = nil;
172 __block OTAccountMetadataClassC* oldState = nil;
174 dispatch_sync(self.queue, ^void {
175 oldState = [self _onqueueLoadOrCreateAccountMetadata:&localError];
180 newState = makeChanges([oldState copy]);
181 if(newState && ![newState saveToKeychainForContainer:self.containerName contextID:self.contextID error:&localError]) {
186 if(localError && error) {
191 [self asyncNotifyAccountStateChanges:newState from:oldState];
198 - (BOOL)persistLastHealthCheck:(NSDate*)lastCheck
199 error:(NSError**)error
201 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
202 metadata.lastHealthCheckup = (uint64_t) ([lastCheck timeIntervalSince1970] * 1000);
207 - (BOOL)persistOctagonJoinAttempt:(OTAccountMetadataClassC_AttemptedAJoinState)attempt
208 error:(NSError**)error
210 return [self persistAccountChanges:^(OTAccountMetadataClassC *metadata) {
211 metadata.attemptedJoin = attempt;
216 - (BOOL)_onqueuePersistAccountChanges:(OTAccountMetadataClassC* _Nullable (^)(OTAccountMetadataClassC* metadata))makeChanges
217 error:(NSError**)error
219 __block NSError* localError = nil;
220 dispatch_assert_queue(self.queue);
222 OTAccountMetadataClassC *oldState = nil;
223 OTAccountMetadataClassC *newState = nil;
225 oldState = [self _onqueueLoadOrCreateAccountMetadata:&localError];
227 newState = makeChanges([oldState copy]);
229 if(![newState saveToKeychainForContainer:self.containerName contextID:self.contextID error:&localError]) {
234 if (error && localError) {
239 [self asyncNotifyAccountStateChanges:newState from:oldState];
246 - (void)asyncNotifyAccountStateChanges:(OTAccountMetadataClassC *)newState from:(OTAccountMetadataClassC *)oldState
250 dispatch_async(self.notifyQueue, ^{
253 for (id<OTCuttlefishAccountStateHolderNotifier> monitor in self.monitors) {
254 [monitor accountStateUpdated:newState from:oldState];