1 #include <Security/SecureObjectSync/SOSTransport.h>
2 #include <Security/SecureObjectSync/SOSTransportMessage.h>
3 #include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
4 #include <Security/SecureObjectSync/SOSKVSKeys.h>
5 #include <Security/SecureObjectSync/SOSPeerCoder.h>
6 #include <Security/SecureObjectSync/SOSEngine.h>
7 #import "Security/SecureObjectSync/SOSPeerRateLimiter.h"
8 #import "Security/SecureObjectSync/SOSPeerOTRTimer.h"
9 #include <utilities/SecADWrapper.h>
11 #include <utilities/SecCFWrappers.h>
12 #include <utilities/SecPLWrappers.h>
13 #include <SOSInternal.h>
14 #include <Security/SecureObjectSync/SOSAccountPriv.h>
15 #include <SOSCloudKeychainClient.h>
16 #include <securityd/SecItemServer.h> // TODO: Remove this layer violation.
18 NSString* const SecSOSMessageRTT = @"com.apple.security.sos.messagertt";
22 @implementation SOSMessage
24 @synthesize engine = engine;
25 @synthesize account = account;
26 @synthesize circleName = circleName;
28 -(id) initWithAccount:(SOSAccount*)acct andName:(NSString*)name
32 SOSEngineRef e = SOSDataSourceFactoryGetEngineForDataSourceName(acct.factory, (__bridge CFStringRef)name, NULL);
35 circleName = [[NSString alloc]initWithString:name];
40 -(CFTypeRef) SOSTransportMessageGetEngine
45 -(CFStringRef) SOSTransportMessageGetCircleName
47 return (__bridge CFStringRef)(circleName);
50 -(CFIndex) SOSTransportMessageGetTransportType
55 -(SOSAccount*) SOSTransportMessageGetAccount
60 -(bool) SOSTransportMessageSendMessages:(SOSMessage*) transport pm:(CFDictionaryRef) peer_messages err:(CFErrorRef *)error
65 -(bool) SOSTransportMessageFlushChanges:(SOSMessage*) transport err:(CFErrorRef *)error
70 -(bool) SOSTransportMessageSyncWithPeers:(SOSMessage*) transport p:(CFSetRef) peers err:(CFErrorRef *)error{
73 -(bool) SOSTransportMessageCleanupAfterPeerMessages:(SOSMessage*) transport peers:(CFDictionaryRef) peers err:(CFErrorRef*) error{
77 -(bool) SOSTransportMessageSendMessage:(SOSMessage*) transport id:(CFStringRef) peerID messageToSend:(CFDataRef) message err:(CFErrorRef *)error
79 CFDictionaryRef peerMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerID, message, NULL);
81 bool result = [self SOSTransportMessageSendMessages:transport pm:peerMessage err:error];
83 CFReleaseNull(peerMessage);
87 bool SOSEngineHandleCodedMessage(SOSAccount* account, SOSEngineRef engine, CFStringRef peerID, CFDataRef codedMessage, CFErrorRef*error) {
88 __block bool result = true;
89 __block bool somethingChanged = false;
90 result &= SOSEngineWithPeerID(engine, peerID, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *shouldSave) {
91 CFDataRef decodedMessage = NULL;
92 enum SOSCoderUnwrapStatus uwstatus = SOSPeerHandleCoderMessage(peer, coder, peerID, codedMessage, &decodedMessage, shouldSave, error);
93 NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
94 //clear the max retry only if negotiation has finished and a counter exists
95 if(coder != NULL && attemptsPerPeer != nil && !SOSCoderIsCoderInAwaitingState(coder) && ([attemptsPerPeer objectForKey:(__bridge NSString*)peerID] != NULL)){
96 secnotice("otrtimer", "otr negotiation completed! clearing max retry counter");
97 SOSPeerOTRTimerClearMaxRetryCount(account, (__bridge NSString*)peerID);
99 if (uwstatus == SOSCoderUnwrapDecoded) {
100 SOSMessageRef message = NULL;
101 if (decodedMessage && CFDataGetLength(decodedMessage)) {
102 // Only hand non empty messages to the engine, empty messages are an artifact
104 message = SOSMessageCreateWithData(kCFAllocatorDefault, decodedMessage, error);
107 bool engineHandleMessageDoesNotGetToRollbackTransactions = true;
108 result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, &engineHandleMessageDoesNotGetToRollbackTransactions, &somethingChanged, error);
109 CFReleaseSafe(message);
112 result = uwstatus != SOSCoderUnwrapError;
114 CFReleaseNull(decodedMessage);
117 if (somethingChanged) {
118 SecKeychainChanged();
122 SOSCCRequestSyncWithPeer(peerID);
127 -(CFDictionaryRef) SOSTransportMessageHandlePeerMessageReturnsHandledCopy:(SOSMessage*) transport peerMessages:(CFMutableDictionaryRef) circle_peer_messages_table err:(CFErrorRef *)error
129 return CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
132 -(void) SOSTransportMessageCalculateNextTimer:(SOSAccount*)account rtt:(int)rtt peerid:(NSString*)peerid
134 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account,kSOSAccountPeerNegotiationTimeouts, NULL);
136 peerRTTs = [NSMutableDictionary dictionary];
139 NSNumber *timeout = [peerRTTs objectForKey:peerid];
141 if(timeout.intValue < (rtt * 2)){
142 NSNumber* newTimeout = [[NSNumber alloc] initWithInt: (rtt * 2)];
143 [peerRTTs setObject:newTimeout forKey:peerid];
144 secnotice("otrtimer", "New OTR RTT: %d", [newTimeout intValue]);
149 timeout = [[NSNumber alloc]initWithInt:(30 * 2)];
152 timeout = [[NSNumber alloc]initWithInt:(rtt * 2)];
154 [peerRTTs setObject:timeout forKey:peerid];
155 secnotice("otrtimer", "setting initial timeout value to rtt*2: %d", [timeout intValue]);
158 SOSAccountSetValue(account,kSOSAccountPeerNegotiationTimeouts, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
161 -(void) SOSTransportMessageUpdateRTTs:(NSString*)peerid
163 SOSAccount* account = [self SOSTransportMessageGetAccount];
164 NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
165 if(peerToTimeLastSentDict){
166 NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
168 NSDate* currentDate = [NSDate date];
169 int rtt = [currentDate timeIntervalSinceDate:storedDate];
170 secnotice("otrtimer","current date: %@, stored date: %@", currentDate, storedDate);
171 secnotice("otrtimer", "rtt: %d", rtt);
172 [self SOSTransportMessageCalculateNextTimer:account rtt:rtt peerid:peerid];
174 SecADClientPushValueForDistributionKey((__bridge CFStringRef) SecSOSMessageRTT, rtt);
175 [peerToTimeLastSentDict removeObjectForKey:peerid]; //remove last sent message date
176 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
181 -(bool) SOSTransportMessageHandlePeerMessage:(SOSMessage*) transport id:(CFStringRef) peer_id cm:(CFDataRef) codedMessage err:(CFErrorRef *)error
183 [self SOSTransportMessageUpdateRTTs:(__bridge NSString*)peer_id];
185 require_quiet(SecRequirementError(transport.engine != NULL, error, CFSTR("Missing engine")), done);
186 result = SOSEngineHandleCodedMessage([transport SOSTransportMessageGetAccount], (SOSEngineRef)transport.engine, peer_id, codedMessage, error);
191 static PeerRateLimiter* getRateLimiter(SOSPeerRef peer)
193 PeerRateLimiter *limiter = nil;
195 if(!(limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer))){
196 limiter = [[PeerRateLimiter alloc] initWithPeer:peer];
197 SOSPeerSetRateLimiter(peer, (__bridge void*)limiter);
202 static void SOSPeerSetNextTimeToSend(PeerRateLimiter* limiter, int nextTimeToSync, NSString *accessGroup, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
204 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time: %d", nextTimeToSync);
206 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
207 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, nextTimeToSync * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
208 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time to sync: %llu", (nextTimeToSync * NSEC_PER_SEC));
210 [limiter.accessGroupToNextMessageToSend setObject:(__bridge NSData*)message_to_send forKey:accessGroup];
212 CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));
213 CFStringRef accessGroupRetained = CFRetainSafe((__bridge CFStringRef)accessGroup);
215 dispatch_source_set_event_handler(timer, ^{
216 SOSCCPeerRateLimiterSendNextMessage_Server(peerid, accessGroupRetained);
219 dispatch_source_set_cancel_handler(timer, ^{
220 CFReleaseSafe(peerid);
221 CFReleaseSafe(accessGroupRetained);
224 dispatch_resume(timer);
225 [limiter.accessGroupToTimer setObject:timer forKey:accessGroup];
228 static void setRateLimitingCounters(SOSAccount* account, PeerRateLimiter* limiter, NSString* attribute)
230 CFErrorRef error = NULL;
232 NSMutableDictionary* counters = (__bridge NSMutableDictionary*) SOSAccountGetValue(account, kSOSRateLimitingCounters, &error);
235 counters = [[NSMutableDictionary alloc]init];
238 [counters setObject:[limiter diagnostics] forKey:attribute];
239 SOSAccountSetValue(account, kSOSRateLimitingCounters, (__bridge CFDictionaryRef)counters, &error);
241 //figure out whether or not an access group should be judged, held back, or sent
242 static bool SOSPeerShouldSend(CFArrayRef attributes, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
244 NSDate *date = [NSDate date];
245 __block bool peerShouldSend = false;
246 PeerRateLimiter *limiter = getRateLimiter(peer);
247 __block NSMutableDictionary *powerlogPayload = nil;
248 static NSMutableDictionary* attributeRateLimiting = nil;
250 static dispatch_once_t onceToken;
251 dispatch_once(&onceToken, ^{
252 attributeRateLimiting = [[NSMutableDictionary alloc] init];
256 peerShouldSend = true;
259 secnotice("ratelimit", "number of attributes to review: %lu", CFArrayGetCount(attributes));
260 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
261 enum RateLimitState state = [limiter stateForAccessGroup:attribute];
264 case RateLimitStateCanSend:
267 KeychainItem *item = [[KeychainItem alloc] initWithAccessGroup: attribute];
268 NSInteger badness = [limiter judge:item at:date limitTime:&limit];
269 secnotice("ratelimit","accessGroup: %@, judged: %lu", attribute, (long)badness);
271 NSNumber *currentBadness = attributeRateLimiting[attribute];
272 NSNumber *newBadness = @(badness);
274 if (![currentBadness isEqual:newBadness]) {
275 attributeRateLimiting[attribute] = newBadness;
276 if (powerlogPayload == NULL) {
277 powerlogPayload = [[NSMutableDictionary alloc] init];
279 powerlogPayload[attribute] = newBadness;
282 double delta = [limit timeIntervalSinceDate:date];
285 //setting counters for attribute being rate limited
286 setRateLimitingCounters([transport SOSTransportMessageGetAccount],limiter, attribute);
288 secnotice("ratelimit", "setting a timer for next sync: %@", limit);
290 SOSPeerSetNextTimeToSend(limiter, delta, attribute, peer, transport, message_to_send);
291 //set rate limit state to hold
292 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateHoldMessage] forKey:attribute];
294 peerShouldSend = true;
298 case RateLimitStateHoldMessage:
300 secnotice("ratelimit","access group: %@ is being rate limited", attribute);
305 secnotice("ratelimit","no state for limiter for peer: %@", peer);
310 if ([powerlogPayload count]) {
311 SecPLLogRegisteredEvent(@"SOSKVSRateLimitingEvent",
313 @"timestamp" : @([date timeIntervalSince1970]),
314 @"peerShouldSend" : @(peerShouldSend),
315 @"attributeBadness" : powerlogPayload
318 return peerShouldSend;
321 //if a separate message containing a rate limited access group is going to be sent, make sure to send any pending messages first otherwise the OTR ordering will be wrong!
322 static void SOSTransportSendPendingMessage(CFArrayRef attributes, SOSMessage* transport, SOSPeerRef peer){
323 PeerRateLimiter *limiter = getRateLimiter(peer);
325 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
326 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:attribute];
328 CFErrorRef error = NULL;
329 bool sendResult = [transport SOSTransportMessageSendMessage:transport id:SOSPeerGetID(peer) messageToSend:(__bridge CFDataRef)message err:&error];
331 if(!sendResult || error){
332 secnotice("ratelimit", "SOSTransportSendPendingMessage: could not send message: %@", error);
335 secnotice("ratelimit", "SOSTransportSendPendingMessage: sent pending message: %@ for access group: %@", message, attribute);
337 [limiter.accessGroupToNextMessageToSend removeObjectForKey:attribute];
338 //cancel dispatch timer
339 dispatch_source_t timer = [limiter.accessGroupToTimer objectForKey:attribute];
341 dispatch_cancel(timer);
342 [limiter.accessGroupToTimer removeObjectForKey:attribute];
343 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:attribute];
348 //update last time message sent for this peer
349 -(void) SOSTransportMessageUpdateLastMessageSentTimetstamp:(SOSAccount*)account peer:(SOSPeerRef)peer
351 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
353 peerRTTs = [NSMutableDictionary dictionary];
355 if([peerRTTs objectForKey:(__bridge NSString*)SOSPeerGetID(peer) ] == nil){
356 [peerRTTs setObject:[NSDate date] forKey:(__bridge NSString*)SOSPeerGetID(peer)];
357 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
360 -(bool) SOSTransportMessageSendMessageIfNeeded:(SOSMessage*) transport id:(CFStringRef) circle_id pID:(CFStringRef) peer_id err:(CFErrorRef *)error
362 __block bool ok = true;
363 SOSAccount* account = transport.account;
365 BOOL initialSync = account.isInitialSyncing;
366 ok &= SOSEngineWithPeerID((SOSEngineRef)transport.engine, peer_id, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
367 // Now under engine lock do stuff
368 CFDataRef message_to_send = NULL;
369 SOSEnginePeerMessageSentBlock sent = NULL;
370 CFMutableArrayRef attributes = NULL;
371 ok = SOSPeerCoderSendMessageIfNeeded([transport SOSTransportMessageGetAccount],(SOSEngineRef)transport.engine, txn, peer, coder, &message_to_send, peer_id, &attributes, &sent, error);
372 secnotice("ratelimit","attribute list: %@", attributes);
373 bool shouldSend = true;
375 if(attributes == NULL){ //no attribute but still should be rate limited
376 attributes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
377 CFArrayAppendValue(attributes, CFSTR("NoAttribute"));
380 secnotice("ratelimit","not going to rate limit, currently in initial sync");
382 if(!initialSync && message_to_send){ //need to judge the message if not in initial sync
383 secnotice("ratelimit","not in initial sync!");
384 shouldSend = SOSPeerShouldSend(attributes, peer, transport, message_to_send);
385 secnotice("ratelimit","should send? : %d", shouldSend);
387 if (shouldSend && message_to_send) {
388 SOSTransportSendPendingMessage(attributes, transport, peer);
389 ok = ok && [transport SOSTransportMessageSendMessage:transport id:peer_id messageToSend:message_to_send err:error];
390 SOSPeerCoderConsume(&sent, ok);
391 [transport SOSTransportMessageUpdateLastMessageSentTimetstamp:account peer:peer];
393 }else if(!shouldSend){
394 secnotice("ratelimit", "peer is rate limited: %@", peer_id);
396 secnotice("transport", "no message to send to peer: %@", peer_id);
399 CFReleaseSafe(message_to_send);
401 *forceSaveState = ok;