diff --git a/.gitignore b/.gitignore index fad27fa0b0..607857d21f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,17 @@ -*.pbxuser -*.mode1v3 -*.mode2v3 -xcuserdata .DS_Store +build +*.mode1v3 +*.pbxuser project.xcworkspace +xcuserdata +.svn +DerivedData +*.orig +*.xccheckout +*.xcscmblueprint +XMPPFramework*.zip + +Pods/ +Carthage/ +/.build +/.swiftpm/ diff --git a/.gitmodules b/.gitmodules index 76e6eff0ac..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +0,0 @@ -[submodule "Vendor/facebook-ios-sdk"] - path = Vendor/facebook-ios-sdk - url = git://github.com/facebook/facebook-ios-sdk.git - ignore = dirty diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000000..5186d07068 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.0 diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SPM_XMPPFramework-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SPM_XMPPFramework-Package.xcscheme new file mode 100644 index 0000000000..c082df66a4 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SPM_XMPPFramework-Package.xcscheme @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..ee1a7e9ff3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,49 @@ +language: objective-c + +jobs: + include: + - stage: Swift PM + osx_image: xcode12.2 + script: + # Remove the Xcode project so `xcodebuild` would be able to test the Swift PM project. + - rm -rf *.xcodeproj + # Test the Swift PM project using `xcodebuild`. + # The test is performed using `xcodebuild` rather than `swift test` due to this bug: + # https://bugs.swift.org/browse/SR-13560 + - xcodebuild -scheme SPM_XMPPFramework-Package -destination "name=iPhone 11 Pro" test + + - stage: Xcode Tests + osx_image: xcode12.2 + cache: + directories: + - Carthage + - Xcode/Testing-iOS/Pods + - Xcode/Testing-macOS/Pods + + before_install: + # - brew update + # - brew outdated carthage || brew upgrade carthage + - export IOS_SIMULATOR_DESTINATION="platform=iOS Simulator,name=iPhone 8,OS=11.3" + + install: + - carthage bootstrap --no-build + - cd Xcode + - bundle install + - bundle exec pod repo update --silent + - bundle exec pod install --project-directory=Testing-iOS/ + - bundle exec pod install --project-directory=Testing-macOS/ + - cd ../ + + before_script: + - set -o pipefail + + script: + - xcodebuild -workspace Xcode/Testing-macOS/XMPPFrameworkTests.xcworkspace -scheme XMPPFrameworkTests -sdk macosx -destination 'platform=OS X,arch=x86_64' test | xcpretty -c + - travis_retry xcodebuild -workspace Xcode/Testing-iOS/XMPPFrameworkTests.xcworkspace -scheme XMPPFrameworkTests -sdk iphonesimulator -destination "$IOS_SIMULATOR_DESTINATION" test | xcpretty -c + - xcodebuild -workspace Xcode/Testing-macOS/XMPPFrameworkTests.xcworkspace -scheme XMPPFrameworkSwiftTests -sdk macosx -destination 'platform=OS X,arch=x86_64' test | xcpretty -c + - travis_retry xcodebuild -workspace Xcode/Testing-iOS/XMPPFrameworkTests.xcworkspace -scheme XMPPFrameworkSwiftTests -sdk iphonesimulator -destination "$IOS_SIMULATOR_DESTINATION" test | xcpretty -c + + - xcodebuild -project XMPPFramework.xcodeproj -scheme "XMPPFramework (tvOS)" -sdk appletvsimulator -arch x86_64 build | xcpretty -c + # - xcodebuild -project Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj -scheme "XMPPFrameworkTests (macOS)" -sdk macosx -arch x86_64 test | xcpretty -c + # - xcodebuild -project Xcode/Testing-Carthage/XMPPFrameworkSwiftTests.xcodeproj -scheme "XMPPFrameworkTests (macOS)" -sdk macosx -arch x86_64 test | xcpretty -c + # - travis_retry xcodebuild -project Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj -scheme "XMPPFrameworkTests (iOS)" -sdk iphonesimulator -destination "$IOS_SIMULATOR_DESTINATION" test | xcpretty -c diff --git a/Authentication/Anonymous/XMPPAnonymousAuthentication.h b/Authentication/Anonymous/XMPPAnonymousAuthentication.h index 94b5515a31..c3fc4cb590 100644 --- a/Authentication/Anonymous/XMPPAnonymousAuthentication.h +++ b/Authentication/Anonymous/XMPPAnonymousAuthentication.h @@ -2,10 +2,10 @@ #import "XMPPSASLAuthentication.h" #import "XMPP.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPAnonymousAuthentication : NSObject -- (id)initWithStream:(XMPPStream *)stream; +- (instancetype)initWithStream:(XMPPStream *)stream; // This class implements the XMPPSASLAuthentication protocol. // @@ -25,7 +25,7 @@ * This information is available after the stream is connected. * In other words, after the delegate has received xmppStreamDidConnect: notification. **/ -- (BOOL)supportsAnonymousAuthentication; +@property (nonatomic, readonly) BOOL supportsAnonymousAuthentication; /** * This method attempts to start the anonymous authentication process. @@ -43,3 +43,4 @@ - (BOOL)authenticateAnonymously:(NSError **)errPtr; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/Anonymous/XMPPAnonymousAuthentication.m b/Authentication/Anonymous/XMPPAnonymousAuthentication.m index 3473139f83..8c5a041829 100644 --- a/Authentication/Anonymous/XMPPAnonymousAuthentication.m +++ b/Authentication/Anonymous/XMPPAnonymousAuthentication.m @@ -70,11 +70,11 @@ - (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse if ([[authResponse name] isEqualToString:@"success"]) { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } @@ -109,7 +109,7 @@ - (BOOL)authenticateAnonymously:(NSError **)errPtr else { NSString *errMsg = @"The server does not support anonymous authentication."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; diff --git a/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.h b/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.h index 4993f03a35..528426afb2 100644 --- a/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.h +++ b/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.h @@ -2,7 +2,7 @@ #import "XMPPSASLAuthentication.h" #import "XMPPStream.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPDeprecatedDigestAuthentication : NSObject // This class implements the XMPPSASLAuthentication protocol. @@ -17,6 +17,7 @@ @interface XMPPStream (XMPPDeprecatedDigestAuthentication) -- (BOOL)supportsDeprecatedDigestAuthentication; +@property (nonatomic, readonly) BOOL supportsDeprecatedDigestAuthentication; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m b/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m index c39b521d26..ca8c9fbcd5 100644 --- a/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m +++ b/Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m @@ -89,11 +89,11 @@ - (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse if ([[authResponse attributeStringValueForName:@"type"] isEqualToString:@"error"]) { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } else { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } } diff --git a/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.h b/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.h index b86efa3038..1fbfcf179a 100644 --- a/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.h +++ b/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.h @@ -2,7 +2,7 @@ #import "XMPPSASLAuthentication.h" #import "XMPPStream.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPDeprecatedPlainAuthentication : NSObject // This class implements the XMPPSASLAuthentication protocol. @@ -17,6 +17,7 @@ @interface XMPPStream (XMPPDeprecatedPlainAuthentication) -- (BOOL)supportsDeprecatedPlainAuthentication; +@property (nonatomic, readonly) BOOL supportsDeprecatedPlainAuthentication; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m b/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m index f6c36b4824..77554653df 100644 --- a/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m +++ b/Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m @@ -83,11 +83,11 @@ - (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse if ([[authResponse attributeStringValueForName:@"type"] isEqualToString:@"error"]) { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } else { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } } diff --git a/Authentication/Digest-MD5/XMPPDigestMD5Authentication.h b/Authentication/Digest-MD5/XMPPDigestMD5Authentication.h index b22878188a..21445b0eae 100644 --- a/Authentication/Digest-MD5/XMPPDigestMD5Authentication.h +++ b/Authentication/Digest-MD5/XMPPDigestMD5Authentication.h @@ -2,7 +2,7 @@ #import "XMPPSASLAuthentication.h" #import "XMPPStream.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPDigestMD5Authentication : NSObject // This class implements the XMPPSASLAuthentication protocol. @@ -17,6 +17,7 @@ @interface XMPPStream (XMPPDigestMD5Authentication) -- (BOOL)supportsDigestMD5Authentication; +@property (nonatomic, readonly) BOOL supportsDigestMD5Authentication; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/Digest-MD5/XMPPDigestMD5Authentication.m b/Authentication/Digest-MD5/XMPPDigestMD5Authentication.m index 5e3fd636a2..e4ec45aba3 100644 --- a/Authentication/Digest-MD5/XMPPDigestMD5Authentication.m +++ b/Authentication/Digest-MD5/XMPPDigestMD5Authentication.m @@ -70,10 +70,16 @@ + (NSString *)mechanismName @synthesize password; - (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword +{ + return [self initWithStream:stream username:nil password:inPassword]; +} + +- (id)initWithStream:(XMPPStream *)stream username:(NSString *)inUsername password:(NSString *)inPassword { if ((self = [super init])) { xmppStream = stream; + username = inUsername; password = inPassword; } return self; @@ -103,16 +109,16 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse if (![[authResponse name] isEqualToString:@"challenge"]) { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } // Extract components from incoming challenge NSDictionary *auth = [self dictionaryFromChallenge:authResponse]; - realm = [auth objectForKey:@"realm"]; - nonce = [auth objectForKey:@"nonce"]; - qop = [auth objectForKey:@"qop"]; + realm = auth[@"realm"]; + nonce = auth[@"nonce"]; + qop = auth[@"qop"]; // Fill out all the other variables // @@ -140,7 +146,10 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse if (cnonce == nil) cnonce = [XMPPStream generateUUID]; - username = [myJID user]; + if (username == nil) + { + username = [myJID user]; + } // Create and send challenge response element @@ -150,7 +159,7 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse [xmppStream sendAuthElement:response]; awaitingChallenge = NO; - return XMPP_AUTH_CONTINUE; + return XMPPHandleAuthResponseContinue; } - (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse @@ -160,14 +169,14 @@ - (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse if ([[authResponse name] isEqualToString:@"challenge"]) { NSDictionary *auth = [self dictionaryFromChallenge:authResponse]; - NSString *rspauth = [auth objectForKey:@"rspauth"]; + NSString *rspauth = auth[@"rspauth"]; if (rspauth == nil) { // We're getting another challenge? // Not sure what this could possibly be, so for now we'll assume it's a failure. - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } else { @@ -182,16 +191,16 @@ - (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse [xmppStream sendAuthElement:response]; - return XMPP_AUTH_CONTINUE; + return XMPPHandleAuthResponseContinue; } } else if ([[authResponse name] isEqualToString:@"success"]) { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } @@ -244,7 +253,7 @@ - (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge if(key && value) { - [auth setObject:value forKey:key]; + auth[key] = value; } } } diff --git a/Authentication/Plain/XMPPPlainAuthentication.h b/Authentication/Plain/XMPPPlainAuthentication.h index a1f79f1ccb..b8eaacc65f 100644 --- a/Authentication/Plain/XMPPPlainAuthentication.h +++ b/Authentication/Plain/XMPPPlainAuthentication.h @@ -2,7 +2,7 @@ #import "XMPPSASLAuthentication.h" #import "XMPPStream.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPPlainAuthentication : NSObject // This class implements the XMPPSASLAuthentication protocol. @@ -17,6 +17,7 @@ @interface XMPPStream (XMPPPlainAuthentication) -- (BOOL)supportsPlainAuthentication; +@property (nonatomic, readonly) BOOL supportsPlainAuthentication; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/Plain/XMPPPlainAuthentication.m b/Authentication/Plain/XMPPPlainAuthentication.m index 590e7111ea..27181798b1 100644 --- a/Authentication/Plain/XMPPPlainAuthentication.m +++ b/Authentication/Plain/XMPPPlainAuthentication.m @@ -25,6 +25,7 @@ @implementation XMPPPlainAuthentication __unsafe_unretained XMPPStream *xmppStream; #endif + NSString *username; NSString *password; } @@ -34,10 +35,16 @@ + (NSString *)mechanismName } - (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword +{ + return [self initWithStream:stream username:nil password:inPassword]; +} + +- (id)initWithStream:(XMPPStream *)stream username:(NSString *)inUsername password:(NSString *)inPassword { if ((self = [super init])) { xmppStream = stream; + username = inUsername; password = inPassword; } return self; @@ -54,9 +61,13 @@ - (BOOL)start:(NSError **)errPtr // authcid: authentication identity (username) // passwd : password for authcid - NSString *username = [xmppStream.myJID user]; + NSString *authUsername = username; + if (!authUsername) + { + authUsername = [xmppStream.myJID user]; + } - NSString *payload = [NSString stringWithFormat:@"\0%@\0%@", username, password]; + NSString *payload = [NSString stringWithFormat:@"\0%@\0%@", authUsername, password]; NSString *base64 = [[payload dataUsingEncoding:NSUTF8StringEncoding] xmpp_base64Encoded]; // Base-64-Info @@ -79,11 +90,11 @@ - (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse if ([[authResponse name] isEqualToString:@"success"]) { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } diff --git a/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.h b/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.h index cf741e76b0..d9ab1ab875 100644 --- a/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.h +++ b/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.h @@ -10,12 +10,14 @@ #import "XMPPSASLAuthentication.h" #import "XMPPStream.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPSCRAMSHA1Authentication : NSObject @end @interface XMPPStream (XMPPSCRAMSHA1Authentication) -- (BOOL)supportsSCRAMSHA1Authentication; +@property (nonatomic, readonly) BOOL supportsSCRAMSHA1Authentication; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.m b/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.m index e57d835233..510e6cd68b 100644 --- a/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.m +++ b/Authentication/SCRAM-SHA-1/XMPPSCRAMSHA1Authentication.m @@ -64,10 +64,22 @@ + (NSString *)mechanismName } - (id)initWithStream:(XMPPStream *)stream password:(NSString *)password +{ + return [self initWithStream:stream username:nil password:password]; +} + +- (id)initWithStream:(XMPPStream *)stream username:(NSString *)username password:(NSString *)password { if ((self = [super init])) { xmppStream = stream; - _username = [XMPPStringPrep prepNode:[xmppStream.myJID user]]; + if (username) + { + _username = username; + } + else + { + _username = [XMPPStringPrep prepNode:[xmppStream.myJID user]]; + } _password = [XMPPStringPrep prepPassword:password]; _hashAlgorithm = kCCHmacAlgSHA1; } @@ -103,7 +115,7 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse if (![[authResponse name] isEqualToString:@"challenge"]) { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } NSDictionary *auth = [self dictionaryFromChallenge:authResponse]; @@ -111,9 +123,9 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; - self.combinedNonce = [auth objectForKey:@"r"]; - self.salt = [auth objectForKey:@"s"]; - self.count = [numberFormatter numberFromString:[auth objectForKey:@"i"]]; + self.combinedNonce = auth[@"r"]; + self.salt = auth[@"s"]; + self.count = [numberFormatter numberFromString:auth[@"i"]]; //We have all the necessary information to calculate client proof and server signature if ([self calculateProofs]) { @@ -123,10 +135,10 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse [xmppStream sendAuthElement:response]; self.awaitingChallenge = NO; - return XMPP_AUTH_CONTINUE; + return XMPPHandleAuthResponseContinue; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } @@ -137,17 +149,17 @@ - (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse NSDictionary *auth = [self dictionaryFromChallenge:authResponse]; if ([[authResponse name] isEqual:@"success"]) { - NSString *receivedServerSignature = [auth objectForKey:@"v"]; + NSString *receivedServerSignature = auth[@"v"]; if([self.serverSignatureData isEqualToData:[[receivedServerSignature dataUsingEncoding:NSUTF8StringEncoding] xmpp_base64Decoded]]){ - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } @@ -307,7 +319,7 @@ - (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge if(key && value) { - [auth setObject:value forKey:key]; + auth[key] = value; } } } diff --git a/Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.h b/Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.h deleted file mode 100644 index ef0a97a547..0000000000 --- a/Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.h +++ /dev/null @@ -1,56 +0,0 @@ -#import -#import "XMPPSASLAuthentication.h" -#import "XMPPStream.h" - - -@interface XMPPXFacebookPlatformAuthentication : NSObject - -/** - * You should use this init method (as opposed the one defined in the XMPPSASLAuthentication protocol). -**/ -- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)appId accessToken:(NSString *)accessToken; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface XMPPStream (XMPPXFacebookPlatformAuthentication) - -/** - * Facebook Chat X-FACEBOOK-PLATFORM SASL authentication initialization. - * This is a convienence init method to help configure Facebook Chat. -**/ -- (id)initWithFacebookAppId:(NSString *)fbAppId; - -/** - * The appId can be passed to custom authentication classes. - * For example, the appId is used for Facebook Chat X-FACEBOOK-PLATFORM SASL authentication. -**/ -@property (readwrite, copy) NSString *facebookAppId; - -/** - * Returns whether or not the server supports X-FACEBOOK-PLATFORM authentication. - * - * This information is available after the stream is connected. - * In other words, after the delegate has received xmppStreamDidConnect: notification. -**/ -- (BOOL)supportsXFacebookPlatformAuthentication; - -/** - * This method attempts to start the facebook oauth authentication process. - * - * This method is asynchronous. - * - * If there is something immediately wrong, - * such as the stream is not connected or doesn't have a set appId or accessToken, - * the method will return NO and set the error. - * Otherwise the delegate callbacks are used to communicate auth success or failure. - * - * @see xmppStreamDidAuthenticate: - * @see xmppStream:didNotAuthenticate: - **/ -- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr; - -@end diff --git a/Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m b/Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m deleted file mode 100644 index 1e473e67e1..0000000000 --- a/Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m +++ /dev/null @@ -1,327 +0,0 @@ -#import "XMPPXFacebookPlatformAuthentication.h" -#import "XMPP.h" -#import "XMPPLogging.h" -#import "XMPPInternal.h" -#import "NSData+XMPP.h" - -#import - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -// Log levels: off, error, warn, info, verbose -#if DEBUG - static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE; -#else - static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; -#endif - -static NSString *const XMPPFacebookChatHostName = @"chat.facebook.com"; - -static char facebookAppIdKey; - -@interface XMPPXFacebookPlatformAuthentication () -{ - #if __has_feature(objc_arc_weak) - __weak XMPPStream *xmppStream; - #else - __unsafe_unretained XMPPStream *xmppStream; - #endif - - BOOL awaitingChallenge; - - NSString *appId; - NSString *accessToken; - NSString *nonce; - NSString *method; -} - -- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge; -- (NSString *)base64EncodedFullResponse; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation XMPPXFacebookPlatformAuthentication - -+ (NSString *)mechanismName -{ - return @"X-FACEBOOK-PLATFORM"; -} - -- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password -{ - if ((self = [super init])) - { - xmppStream = stream; - } - return self; -} - -- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)inAppId accessToken:(NSString *)inAccessToken -{ - if ((self = [super init])) - { - xmppStream = stream; - appId = inAppId; - accessToken = inAccessToken; - } - return self; -} - -- (BOOL)start:(NSError **)errPtr -{ - if (!appId || !accessToken) - { - NSString *errMsg = @"Missing facebook appId and/or accessToken."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; - - if (errPtr) *errPtr = err; - return NO; - } - - // - - NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; - [auth addAttributeWithName:@"mechanism" stringValue:@"X-FACEBOOK-PLATFORM"]; - - [xmppStream sendAuthElement:auth]; - awaitingChallenge = YES; - - return YES; -} - -- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse -{ - XMPPLogTrace(); - - // We're expecting a challenge response. - // If we get anything else we're going to assume it's some kind of failure response. - - if (![[authResponse name] isEqualToString:@"challenge"]) - { - return XMPP_AUTH_FAIL; - } - - // Extract components from incoming challenge - - NSDictionary *auth = [self dictionaryFromChallenge:authResponse]; - - nonce = [auth objectForKey:@"nonce"]; - method = [auth objectForKey:@"method"]; - - // Create and send challenge response element - - NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; - [response setStringValue:[self base64EncodedFullResponse]]; - - [xmppStream sendAuthElement:response]; - awaitingChallenge = NO; - - return XMPP_AUTH_CONTINUE; -} - -- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse -{ - XMPPLogTrace(); - - // We're expecting a success response. - // If we get anything else we can safely assume it's the equivalent of a failure response. - - if ([[authResponse name] isEqualToString:@"success"]) - { - return XMPP_AUTH_SUCCESS; - } - else - { - return XMPP_AUTH_FAIL; - } -} - -- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse -{ - if (awaitingChallenge) - { - return [self handleAuth1:authResponse]; - } - else - { - return [self handleAuth2:authResponse]; - } -} - -- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge -{ - // The value of the challenge stanza is base 64 encoded. - // Once "decoded", it's just a string of key=value pairs separated by ampersands. - - NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding]; - NSData *decodedData = [base64Data xmpp_base64Decoded]; - - NSString *authStr = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; - - XMPPLogVerbose(@"%@: decoded challenge: %@", THIS_FILE, authStr); - - NSArray *components = [authStr componentsSeparatedByString:@"&"]; - NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:3]; - - for (NSString *component in components) - { - NSRange separator = [component rangeOfString:@"="]; - if (separator.location != NSNotFound) - { - NSString *key = [[component substringToIndex:separator.location] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - NSString *value = [[component substringFromIndex:separator.location+1] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2) - { - // Strip quotes from value - value = [value substringWithRange:NSMakeRange(1,([value length]-2))]; - } - - [auth setObject:value forKey:key]; - } - } - - return auth; -} - -- (NSString *)base64EncodedFullResponse -{ - if (!appId || !accessToken || !method || !nonce) - { - return nil; - } - - srand([[NSDate date] timeIntervalSince1970]); - - NSMutableString *buffer = [NSMutableString stringWithCapacity:250]; - [buffer appendFormat:@"method=%@&", method]; - [buffer appendFormat:@"nonce=%@&", nonce]; - [buffer appendFormat:@"access_token=%@&", accessToken]; - [buffer appendFormat:@"api_key=%@&", appId]; - [buffer appendFormat:@"call_id=%d&", rand()]; - [buffer appendFormat:@"v=%@",@"1.0"]; - - XMPPLogVerbose(@"XMPPXFacebookPlatformAuthentication: response for facebook: %@", buffer); - - NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding]; - - return [utf8data xmpp_base64Encoded]; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation XMPPStream (XMPPXFacebookPlatformAuthentication) - -- (id)initWithFacebookAppId:(NSString *)fbAppId -{ - if ((self = [self init])) // Note: Using [self init], NOT [super init] - { - self.facebookAppId = fbAppId; - self.myJID = [XMPPJID jidWithString:XMPPFacebookChatHostName]; - - // As of October 8, 2011, Facebook doesn't have their XMPP SRV records set. - // And, as per the XMPP specification, we MUST check the XMPP SRV records for an IP address, - // before falling back to a traditional A record lookup. - // - // So we're setting the hostname as a minor optimization to avoid the SRV timeout delay. - - self.hostName = XMPPFacebookChatHostName; - } - return self; -} - -- (NSString *)facebookAppId -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - result = objc_getAssociatedObject(self, &facebookAppIdKey); - }; - - if (dispatch_get_specific(self.xmppQueueTag)) - block(); - else - dispatch_sync(self.xmppQueue, block); - - return result; -} - -- (void)setFacebookAppId:(NSString *)inFacebookAppId -{ - NSString *newFacebookAppId = [inFacebookAppId copy]; - - dispatch_block_t block = ^{ - objc_setAssociatedObject(self, &facebookAppIdKey, newFacebookAppId, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - }; - - if (dispatch_get_specific(self.xmppQueueTag)) - block(); - else - dispatch_async(self.xmppQueue, block); -} - -- (BOOL)supportsXFacebookPlatformAuthentication -{ - return [self supportsAuthenticationMechanism:[XMPPXFacebookPlatformAuthentication mechanismName]]; -} - -/** - * This method attempts to connect to the Facebook Chat servers - * using the Facebook OAuth token returned by the Facebook OAuth 2.0 authentication process. -**/ -- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr -{ - XMPPLogTrace(); - - __block BOOL result = YES; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if ([self supportsXFacebookPlatformAuthentication]) - { - XMPPXFacebookPlatformAuthentication *facebookAuth = - [[XMPPXFacebookPlatformAuthentication alloc] initWithStream:self - appId:self.facebookAppId - accessToken:accessToken]; - - result = [self authenticate:facebookAuth error:&err]; - } - else - { - NSString *errMsg = @"The server does not support X-FACEBOOK-PLATFORM authentication."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; - - result = NO; - } - }}; - - if (dispatch_get_specific(self.xmppQueueTag)) - block(); - else - dispatch_sync(self.xmppQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -@end diff --git a/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.h b/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.h index 4720206531..1783567a0f 100644 --- a/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.h +++ b/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.h @@ -10,9 +10,11 @@ #import "XMPPSASLAuthentication.h" #import "XMPPStream.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPXOAuth2Google : NSObject --(id)initWithStream:(XMPPStream *)stream accessToken:(NSString *)accessToken; +-(instancetype)initWithStream:(XMPPStream *)stream + accessToken:(NSString *)accessToken; @end @@ -21,8 +23,9 @@ @interface XMPPStream (XMPPXOAuth2Google) -- (BOOL)supportsXOAuth2GoogleAuthentication; +@property (nonatomic, readonly) BOOL supportsXOAuth2GoogleAuthentication; - (BOOL)authenticateWithGoogleAccessToken:(NSString *)accessToken error:(NSError **)errPtr; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.m b/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.m index 4a10228df9..628ae29279 100644 --- a/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.m +++ b/Authentication/X-OAuth2-Google/XMPPXOAuth2Google.m @@ -78,7 +78,7 @@ - (BOOL)start:(NSError **)errPtr if (!accessToken) { NSString *errMsg = @"Missing facebook accessToken."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -121,11 +121,11 @@ - (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse if ([[authResponse name] isEqualToString:@"success"]) { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } @end @@ -158,7 +158,7 @@ - (BOOL)authenticateWithGoogleAccessToken:(NSString *)accessToken error:(NSError else { NSString *errMsg = @"The server does not support X-OATH2-GOOGLE authentication."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; diff --git a/Authentication/XMPPCustomBinding.h b/Authentication/XMPPCustomBinding.h index fa8348529b..6639ae921e 100644 --- a/Authentication/XMPPCustomBinding.h +++ b/Authentication/XMPPCustomBinding.h @@ -1,25 +1,29 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; typedef NS_ENUM(NSInteger, XMPPBindResult) { - XMPP_BIND_CONTINUE, // The custom binding process is still ongoing. + XMPPBindResultContinue, // The custom binding process is still ongoing. - XMPP_BIND_SUCCESS, // Custom binding succeeded. + XMPPBindResultSuccess, // Custom binding succeeded. // The stream should continue normal post-binding operation. - XMPP_BIND_FAIL_FALLBACK, // Custom binding failed. + XMPPBindResultFailFallback, // Custom binding failed. // The stream should fallback to the standard binding protocol. - XMPP_BIND_FAIL_ABORT // Custom binding failed. + XMPPBindResultFailAbort // Custom binding failed. // The stream must abort the binding process. // Further, because the stream is in a bad state (authenticated, but // unable to complete the full handshake) it must immediately disconnect. // The given NSError will be reported via xmppStreamDidDisconnect:withError: }; +// Legacy fallback for external modules +#define XMPP_BIND_CONTINUE XMPPBindResultContinue +#define XMPP_BIND_SUCCESS XMPPBindResultSuccess +#define XMPP_BIND_FAIL_FALLBACK XMPPBindResultFailFallback +#define XMPP_BIND_FAIL_ABORT XMPPBindResultFailAbort + /** * Binding a JID resource is a standard part of the authentication process, * and occurs after SASL authentication completes (which generally authenticates the JID username). @@ -34,6 +38,7 @@ typedef NS_ENUM(NSInteger, XMPPBindResult) { * A custom binding procedure may be plugged into an XMPPStream instance via the delegate method: * - (id )xmppStreamWillBind; **/ +NS_ASSUME_NONNULL_BEGIN @protocol XMPPCustomBinding @required @@ -41,16 +46,16 @@ typedef NS_ENUM(NSInteger, XMPPBindResult) { * Attempts to start the custom binding process. * * If it isn't possible to start the process (perhaps due to missing information), - * this method should return XMPP_BIND_FAIL_FALLBACK or XMPP_BIND_FAIL_ABORT. + * this method should return XMPPBindResultFailFallback or XMPPBindResultFailAbort. * - * (The error message is only used by xmppStream if this method returns XMPP_BIND_FAIL_ABORT.) + * (The error message is only used by xmppStream if this method returns XMPPBindResultFailAbort.) * * If binding isn't needed (for example, because custom SASL authentication already handled it), - * this method should return XMPP_BIND_SUCCESS. + * this method should return XMPPBindResultSuccess. * In this case, xmppStream will immediately move to its post-binding operations. * * Otherwise this method should send whatever stanzas are needed to begin the binding process. - * And then return XMPP_BIND_CONTINUE. + * And then return XMPPBindResultContinue. * * This method is called by automatically XMPPStream. * You MUST NOT invoke this method manually. @@ -60,7 +65,7 @@ typedef NS_ENUM(NSInteger, XMPPBindResult) { /** * After the custom binding process has started, all incoming xmpp stanzas are routed to this method. * The method should process the stanza as appropriate, and return the coresponding result. - * If the process is not yet complete, it should return XMPP_BIND_CONTINUE, + * If the process is not yet complete, it should return XMPPBindResultContinue, * meaning the xmpp stream will continue to forward all incoming xmpp stanzas to this method. * * This method is called automatically by XMPPStream. @@ -91,3 +96,4 @@ typedef NS_ENUM(NSInteger, XMPPBindResult) { - (BOOL)shouldSkipStartSessionAfterSuccessfulBinding; @end +NS_ASSUME_NONNULL_END diff --git a/Authentication/XMPPSASLAuthentication.h b/Authentication/XMPPSASLAuthentication.h index 9d0d1df921..58aa06135b 100644 --- a/Authentication/XMPPSASLAuthentication.h +++ b/Authentication/XMPPSASLAuthentication.h @@ -1,23 +1,26 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; @class XMPPStream; typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) { - XMPP_AUTH_FAIL, // Authentication failed. + XMPPHandleAuthResponseFailed, // Authentication failed. // The delegate will be informed via xmppStream:didNotAuthenticate: - XMPP_AUTH_SUCCESS, // Authentication succeeded. + XMPPHandleAuthResponseSuccess, // Authentication succeeded. // The delegate will be informed via xmppStreamDidAuthenticate: - XMPP_AUTH_CONTINUE, // The authentication process is still ongoing. + XMPPHandleAuthResponseContinue, // The authentication process is still ongoing. }; +// Legacy fallback +#define XMPP_AUTH_FAIL XMPPHandleAuthResponseFailed +#define XMPP_AUTH_SUCCESS XMPPHandleAuthResponseSuccess +#define XMPP_AUTH_CONTINUE XMPPHandleAuthResponseContinue +NS_ASSUME_NONNULL_BEGIN @protocol XMPPSASLAuthentication @required @@ -37,7 +40,7 @@ typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) { * * The mechanismName returned should match the value inside the HERE. **/ -+ (NSString *)mechanismName; ++ (nullable NSString *)mechanismName; /** * Standard init method. @@ -56,7 +59,8 @@ typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) { * In this case, the authentication mechanism class should provide it's own custom init method. * However it should still implement this method, and then use the start method to notify of errors. **/ -- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password; +- (instancetype)initWithStream:(XMPPStream *)stream + password:(NSString *)password; /** * Attempts to start the authentication process. @@ -75,7 +79,7 @@ typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) { /** * After the authentication process has started, all incoming xmpp stanzas are routed to this method. * The authentication mechanism should process the stanza as appropriate, and return the coresponding result. - * If the authentication is not yet complete, it should return XMPP_AUTH_CONTINUE, + * If the authentication is not yet complete, it should return XMPPHandleAuthResponseContinue, * meaning the xmpp stream will continue to forward all incoming xmpp stanzas to this method. * * This method is called automatically by XMPPStream (via the authenticate: method). @@ -85,6 +89,15 @@ typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) { @optional +/** + * Use this init method if the username used for authentication does not match the user part of the JID. + * If username is nil, the user part of the JID will be used. + * The standard init method uses this init method, passing nil for the username. + **/ +- (instancetype)initWithStream:(XMPPStream *)stream + username:(nullable NSString *)username + password:(NSString *)password; + /** * Optionally implement this method to override the default behavior. * The default value is YES. @@ -92,3 +105,4 @@ typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) { - (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication; @end +NS_ASSUME_NONNULL_END diff --git a/Cartfile b/Cartfile new file mode 100644 index 0000000000..323cc8035a --- /dev/null +++ b/Cartfile @@ -0,0 +1,4 @@ +github "CocoaLumberjack/CocoaLumberjack" ~> 3.3.0 +github "robbiehanson/CocoaAsyncSocket" ~> 7.6.2 +github "robbiehanson/KissXML" ~> 5.2.1 +github "chrisballinger/libidn-framework" ~> 1.33.1 diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 0000000000..116dd2eb30 --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1,4 @@ +github "CocoaLumberjack/CocoaLumberjack" "3.4.2" +github "chrisballinger/libidn-framework" "1.35" +github "robbiehanson/CocoaAsyncSocket" "7.6.3" +github "robbiehanson/KissXML" "5.2.3" diff --git a/Categories/NSData+XMPP.h b/Categories/NSData+XMPP.h index 593abdc0e4..8501ba98d6 100644 --- a/Categories/NSData+XMPP.h +++ b/Categories/NSData+XMPP.h @@ -1,19 +1,20 @@ #import +NS_ASSUME_NONNULL_BEGIN @interface NSData (XMPP) -- (NSData *)xmpp_md5Digest; +@property (nonatomic, readonly) NSData *xmpp_md5Digest; -- (NSData *)xmpp_sha1Digest; +@property (nonatomic, readonly) NSData *xmpp_sha1Digest; -- (NSString *)xmpp_hexStringValue; +@property (nonatomic, readonly) NSString *xmpp_hexStringValue; -- (NSString *)xmpp_base64Encoded; -- (NSData *)xmpp_base64Decoded; +@property (nonatomic, readonly) NSString *xmpp_base64Encoded; +@property (nonatomic, readonly) NSData *xmpp_base64Decoded; -- (BOOL)xmpp_isJPEG; -- (BOOL)xmpp_isPNG; -- (NSString *)xmpp_imageType; +@property (nonatomic, readonly) BOOL xmpp_isJPEG; +@property (nonatomic, readonly) BOOL xmpp_isPNG; +@property (nonatomic, readonly, nullable) NSString *xmpp_imageType; @end @@ -32,3 +33,4 @@ #undef XMPP_DEPRECATED #endif +NS_ASSUME_NONNULL_END diff --git a/Categories/NSNumber+XMPP.h b/Categories/NSNumber+XMPP.h index 2c4cff6a21..4d76d5183f 100644 --- a/Categories/NSNumber+XMPP.h +++ b/Categories/NSNumber+XMPP.h @@ -1,10 +1,10 @@ #import - +NS_ASSUME_NONNULL_BEGIN @interface NSNumber (XMPP) + (NSNumber *)xmpp_numberWithPtr:(const void *)ptr; -- (id)xmpp_initWithPtr:(const void *)ptr __attribute__((objc_method_family(init))); +- (instancetype)xmpp_initWithPtr:(const void *)ptr __attribute__((objc_method_family(init))); + (BOOL)xmpp_parseString:(NSString *)str intoInt32:(int32_t *)pNum; + (BOOL)xmpp_parseString:(NSString *)str intoUInt32:(uint32_t *)pNum; @@ -44,3 +44,4 @@ #undef XMPP_DEPRECATED #endif +NS_ASSUME_NONNULL_END diff --git a/Categories/NSNumber+XMPP.m b/Categories/NSNumber+XMPP.m index 01bdb916ff..495dd16459 100644 --- a/Categories/NSNumber+XMPP.m +++ b/Categories/NSNumber+XMPP.m @@ -12,7 +12,7 @@ + (NSNumber *)xmpp_numberWithPtr:(const void *)ptr return [[NSNumber alloc] xmpp_initWithPtr:ptr]; } -- (id)xmpp_initWithPtr:(const void *)ptr +- (instancetype)xmpp_initWithPtr:(const void *)ptr { return [self initWithLong:(long)ptr]; } diff --git a/Categories/NSXMLElement+XMPP.h b/Categories/NSXMLElement+XMPP.h index addf89acc2..4d8d7321db 100644 --- a/Categories/NSXMLElement+XMPP.h +++ b/Categories/NSXMLElement+XMPP.h @@ -1,10 +1,8 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif - +@import KissXML; +NS_ASSUME_NONNULL_BEGIN @interface NSXMLElement (XMPP) /** @@ -12,10 +10,10 @@ **/ + (NSXMLElement *)elementWithName:(NSString *)name numberValue:(NSNumber *)number; -- (id)initWithName:(NSString *)name numberValue:(NSNumber *)number; +- (instancetype)initWithName:(NSString *)name numberValue:(NSNumber *)number; + (NSXMLElement *)elementWithName:(NSString *)name objectValue:(id)objectValue; -- (id)initWithName:(NSString *)name objectValue:(id)objectValue; +- (instancetype)initWithName:(NSString *)name objectValue:(id)objectValue; /** * Creating elements with explicit xmlns values. @@ -24,23 +22,29 @@ * The category methods below are more readable, and they actually work. **/ +#if !TARGET_OS_IPHONE + (NSXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns; -- (id)initWithName:(NSString *)name xmlns:(NSString *)ns; +#endif + +- (instancetype)initWithName:(NSString *)name xmlns:(NSString *)ns; /** * Extracting multiple elements. **/ -- (NSArray *)elementsForXmlns:(NSString *)ns; -- (NSArray *)elementsForXmlnsPrefix:(NSString *)nsPrefix; +- (NSArray *)elementsForXmlns:(NSString *)ns; +- (NSArray *)elementsForXmlnsPrefix:(NSString *)nsPrefix; /** * Extracting a single element. **/ -- (NSXMLElement *)elementForName:(NSString *)name; -- (NSXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns; -- (NSXMLElement *)elementForName:(NSString *)name xmlnsPrefix:(NSString *)xmlnsPrefix; +#if !TARGET_OS_IPHONE +- (nullable NSXMLElement *)elementForName:(NSString *)name NS_REFINED_FOR_SWIFT; +- (nullable NSXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns NS_REFINED_FOR_SWIFT; +#endif + +- (nullable NSXMLElement *)elementForName:(NSString *)name xmlnsPrefix:(NSString *)xmlnsPrefix NS_SWIFT_NAME(element(forName:xmlnsPrefix:)); /** * Convenience methods for removing child elements. @@ -53,22 +57,24 @@ - (void)removeElementForName:(NSString *)name xmlns:(NSString *)xmlns; - (void)removeElementForName:(NSString *)name xmlnsPrefix:(NSString *)xmlnsPrefix; +#if !TARGET_OS_IPHONE /** * Working with the common xmpp xmlns value. - * + * * Use these instead of getting/setting the URI. * The category methods below are more readable, and they actually work. -**/ + **/ -- (NSString *)xmlns; +@property (nonatomic, readonly, nullable) NSString *xmlns; - (void)setXmlns:(NSString *)ns; /** * Convenience methods for printing xml elements with different styles. -**/ + **/ -- (NSString *)prettyXMLString; -- (NSString *)compactXMLString; +@property (nonatomic, readonly, nullable) NSString *prettyXMLString; +@property (nonatomic, readonly, nullable) NSString *compactXMLString; +#endif /** * Convenience methods for adding attributes. @@ -80,10 +86,12 @@ - (void)addAttributeWithName:(NSString *)name doubleValue:(double)doubleValue; - (void)addAttributeWithName:(NSString *)name integerValue:(NSInteger)integerValue; - (void)addAttributeWithName:(NSString *)name unsignedIntegerValue:(NSUInteger)unsignedIntegerValue; -- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string; - (void)addAttributeWithName:(NSString *)name numberValue:(NSNumber *)number; - (void)addAttributeWithName:(NSString *)name objectValue:(id)objectValue; +#if !TARGET_OS_IPHONE +- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string; +#endif /** * Convenience methods for extracting attribute values in different formats. * @@ -100,17 +108,17 @@ - (uint64_t)attributeUInt64ValueForName:(NSString *)name; - (NSInteger)attributeIntegerValueForName:(NSString *)name; - (NSUInteger)attributeUnsignedIntegerValueForName:(NSString *)name; -- (NSString *)attributeStringValueForName:(NSString *)name; -- (NSNumber *)attributeNumberIntValueForName:(NSString *)name; -- (NSNumber *)attributeNumberBoolValueForName:(NSString *)name; -- (NSNumber *)attributeNumberFloatValueForName:(NSString *)name; -- (NSNumber *)attributeNumberDoubleValueForName:(NSString *)name; -- (NSNumber *)attributeNumberInt32ValueForName:(NSString *)name; -- (NSNumber *)attributeNumberUInt32ValueForName:(NSString *)name; -- (NSNumber *)attributeNumberInt64ValueForName:(NSString *)name; -- (NSNumber *)attributeNumberUInt64ValueForName:(NSString *)name; -- (NSNumber *)attributeNumberIntegerValueForName:(NSString *)name; -- (NSNumber *)attributeNumberUnsignedIntegerValueForName:(NSString *)name; +- (nullable NSString *)attributeStringValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberIntValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberBoolValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberFloatValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberDoubleValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberInt32ValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberUInt32ValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberInt64ValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberUInt64ValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberIntegerValueForName:(NSString *)name; +- (nullable NSNumber *)attributeNumberUnsignedIntegerValueForName:(NSString *)name; - (int)attributeIntValueForName:(NSString *)name withDefaultValue:(int)defaultValue; - (BOOL)attributeBoolValueForName:(NSString *)name withDefaultValue:(BOOL)defaultValue; @@ -126,7 +134,9 @@ - (NSNumber *)attributeNumberIntValueForName:(NSString *)name withDefaultValue:(int)defaultValue; - (NSNumber *)attributeNumberBoolValueForName:(NSString *)name withDefaultValue:(BOOL)defaultValue; -- (NSMutableDictionary *)attributesAsDictionary; +#if !TARGET_OS_IPHONE +@property (nonatomic, readonly) NSMutableDictionary *attributesAsDictionary; +#endif /** * Convenience methods for extracting element values in different formats. @@ -151,7 +161,9 @@ - (void)addNamespaceWithPrefix:(NSString *)prefix stringValue:(NSString *)string; -- (NSString *)namespaceStringValueForPrefix:(NSString *)prefix; +- (nullable NSString *)namespaceStringValueForPrefix:(NSString *)prefix; - (NSString *)namespaceStringValueForPrefix:(NSString *)prefix withDefaultValue:(NSString *)defaultValue; @end + +NS_ASSUME_NONNULL_END diff --git a/Categories/NSXMLElement+XMPP.m b/Categories/NSXMLElement+XMPP.m index 9f516586f1..d86402adea 100644 --- a/Categories/NSXMLElement+XMPP.m +++ b/Categories/NSXMLElement+XMPP.m @@ -61,6 +61,7 @@ - (id)initWithName:(NSString *)name objectValue:(id)objectValue } } +#if !TARGET_OS_IPHONE /** * Quick method to create an element **/ @@ -70,6 +71,7 @@ + (NSXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns [element setXmlns:ns]; return element; } +#endif - (id)initWithName:(NSString *)name xmlns:(NSString *)ns { @@ -120,6 +122,7 @@ - (NSArray *)elementsForXmlnsPrefix:(NSString *)nsPrefix return elements; } +#if !TARGET_OS_IPHONE /** * This method returns the first child element for the given name (as an NSXMLElement). * If no child elements exist for the given name, nil is returned. @@ -129,29 +132,29 @@ - (NSXMLElement *)elementForName:(NSString *)name NSArray *elements = [self elementsForName:name]; if ([elements count] > 0) { - return [elements objectAtIndex:0]; + return elements[0]; } else { // There is a bug in the NSXMLElement elementsForName: method. // Consider the following XML fragment: - // + // // // // - // + // // Calling [query elementsForName:@"x"] results in an empty array! - // + // // However, it will work properly if you use the following: // [query elementsForLocalName:@"x" URI:@"some:other:namespace"] - // + // // The trouble with this is that we may not always know the xmlns in advance, // so in this particular case there is no way to access the element without looping through the children. - // + // // This bug was submitted to apple on June 1st, 2007 and was classified as "serious". - // + // // --!!-- This bug does NOT exist in DDXML --!!-- - + return nil; } } @@ -165,13 +168,14 @@ - (NSXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns NSArray *elements = [self elementsForLocalName:name URI:xmlns]; if ([elements count] > 0) { - return [elements objectAtIndex:0]; + return elements[0]; } else { return nil; } } +#endif - (NSXMLElement *)elementForName:(NSString *)name xmlnsPrefix:(NSString *)xmlnsPrefix{ @@ -250,6 +254,7 @@ - (void)removeElementForName:(NSString *)name xmlnsPrefix:(NSString *)xmlnsPrefi } } +#if !TARGET_OS_IPHONE /** * Returns the common xmlns "attribute", which is only accessible via the namespace methods. * The xmlns value is often used in jabber elements. @@ -282,6 +287,7 @@ - (NSString *)compactXMLString { return [self XMLStringWithOptions:NSXMLNodeCompactEmptyElement]; } +#endif /** * Shortcut to avoid having to use NSXMLNode everytime @@ -289,37 +295,32 @@ - (NSString *)compactXMLString - (void)addAttributeWithName:(NSString *)name intValue:(int)intValue { - [self addAttributeWithName:name numberValue:[NSNumber numberWithInt:intValue]]; + [self addAttributeWithName:name numberValue:@(intValue)]; } - (void)addAttributeWithName:(NSString *)name boolValue:(BOOL)boolValue { - [self addAttributeWithName:name numberValue:[NSNumber numberWithBool:boolValue]]; + [self addAttributeWithName:name numberValue:@(boolValue)]; } - (void)addAttributeWithName:(NSString *)name floatValue:(float)floatValue { - [self addAttributeWithName:name numberValue:[NSNumber numberWithFloat:floatValue]]; + [self addAttributeWithName:name numberValue:@(floatValue)]; } - (void)addAttributeWithName:(NSString *)name doubleValue:(double)doubleValue { - [self addAttributeWithName:name numberValue:[NSNumber numberWithDouble:doubleValue]]; + [self addAttributeWithName:name numberValue:@(doubleValue)]; } - (void)addAttributeWithName:(NSString *)name integerValue:(NSInteger)integerValue { - [self addAttributeWithName:name numberValue:[NSNumber numberWithInteger:integerValue]]; + [self addAttributeWithName:name numberValue:@(integerValue)]; } - (void)addAttributeWithName:(NSString *)name unsignedIntegerValue:(NSUInteger)unsignedIntegerValue { - [self addAttributeWithName:name numberValue:[NSNumber numberWithUnsignedInteger:unsignedIntegerValue]]; -} - -- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string -{ - [self addAttribute:[NSXMLNode attributeWithName:name stringValue:string]]; + [self addAttributeWithName:name numberValue:@(unsignedIntegerValue)]; } - (void)addAttributeWithName:(NSString *)name numberValue:(NSNumber *)number @@ -343,6 +344,13 @@ - (void)addAttributeWithName:(NSString *)name objectValue:(id)objectValue } } +#if !TARGET_OS_IPHONE +- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string + { + [self addAttribute:[NSXMLNode attributeWithName:name stringValue:string]]; + } +#endif + /** * The following methods return the corresponding value of the attribute with the given name. **/ @@ -424,43 +432,43 @@ - (NSString *)attributeStringValueForName:(NSString *)name } - (NSNumber *)attributeNumberIntValueForName:(NSString *)name { - return [NSNumber numberWithInt:[self attributeIntValueForName:name]]; + return @([self attributeIntValueForName:name]); } - (NSNumber *)attributeNumberBoolValueForName:(NSString *)name { - return [NSNumber numberWithBool:[self attributeBoolValueForName:name]]; + return @([self attributeBoolValueForName:name]); } - (NSNumber *)attributeNumberFloatValueForName:(NSString *)name { - return [NSNumber numberWithFloat:[self attributeFloatValueForName:name]]; + return @([self attributeFloatValueForName:name]); } - (NSNumber *)attributeNumberDoubleValueForName:(NSString *)name { - return [NSNumber numberWithDouble:[self attributeDoubleValueForName:name]]; + return @([self attributeDoubleValueForName:name]); } - (NSNumber *)attributeNumberInt32ValueForName:(NSString *)name { - return [NSNumber numberWithInt:[self attributeInt32ValueForName:name]]; + return @([self attributeInt32ValueForName:name]); } - (NSNumber *)attributeNumberUInt32ValueForName:(NSString *)name { - return [NSNumber numberWithUnsignedInt:[self attributeUInt32ValueForName:name]]; + return @([self attributeUInt32ValueForName:name]); } - (NSNumber *)attributeNumberInt64ValueForName:(NSString *)name { - return [NSNumber numberWithLongLong:[self attributeInt64ValueForName:name]]; + return @([self attributeInt64ValueForName:name]); } - (NSNumber *)attributeNumberUInt64ValueForName:(NSString *)name { - return [NSNumber numberWithUnsignedLongLong:[self attributeUInt64ValueForName:name]]; + return @([self attributeUInt64ValueForName:name]); } - (NSNumber *)attributeNumberIntegerValueForName:(NSString *)name { - return [NSNumber numberWithInteger:[self attributeIntegerValueForName:name]]; + return @([self attributeIntegerValueForName:name]); } - (NSNumber *)attributeNumberUnsignedIntegerValueForName:(NSString *)name { - return [NSNumber numberWithUnsignedInteger:[self attributeUnsignedIntegerValueForName:name]]; + return @([self attributeUnsignedIntegerValueForName:name]); } /** @@ -543,13 +551,14 @@ - (NSString *)attributeStringValueForName:(NSString *)name withDefaultValue:(NSS } - (NSNumber *)attributeNumberIntValueForName:(NSString *)name withDefaultValue:(int)defaultValue { - return [NSNumber numberWithInt:[self attributeIntValueForName:name withDefaultValue:defaultValue]]; + return @([self attributeIntValueForName:name withDefaultValue:defaultValue]); } - (NSNumber *)attributeNumberBoolValueForName:(NSString *)name withDefaultValue:(BOOL)defaultValue { - return [NSNumber numberWithBool:[self attributeBoolValueForName:name withDefaultValue:defaultValue]]; + return @([self attributeBoolValueForName:name withDefaultValue:defaultValue]); } +#if !TARGET_OS_IPHONE /** * Returns all the attributes in a dictionary. **/ @@ -561,12 +570,13 @@ - (NSMutableDictionary *)attributesAsDictionary NSUInteger i; for(i = 0; i < [attributes count]; i++) { - NSXMLNode *node = [attributes objectAtIndex:i]; + NSXMLNode *node = attributes[i]; - [result setObject:[node stringValue] forKey:[node name]]; + result[[node name]] = [node stringValue]; } return result; } +#endif /** * The following methods return the corresponding value of the node. diff --git a/Core/Info.plist b/Core/Info.plist new file mode 100644 index 0000000000..fbe1e6b314 --- /dev/null +++ b/Core/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Core/XMPP.h b/Core/XMPP.h index eae4465ba7..736e7a508e 100644 --- a/Core/XMPP.h +++ b/Core/XMPP.h @@ -19,7 +19,6 @@ #import "XMPPDigestMD5Authentication.h" #import "XMPPSCRAMSHA1Authentication.h" #import "XMPPPlainAuthentication.h" -#import "XMPPXFacebookPlatformAuthentication.h" #import "XMPPAnonymousAuthentication.h" #import "XMPPDeprecatedPlainAuthentication.h" #import "XMPPDeprecatedDigestAuthentication.h" diff --git a/Core/XMPPConstants.h b/Core/XMPPConstants.h new file mode 100644 index 0000000000..410a91597f --- /dev/null +++ b/Core/XMPPConstants.h @@ -0,0 +1,18 @@ +#import + +/** +* This class is provided to house various namespaces that are reused throughout +* the project. Feel free to add to the constants as you see necessary. If a +* particular namespace is only applicable to a particular extension, then it +* should be inside that extension rather than here. +*/ + +NS_ASSUME_NONNULL_BEGIN +extern NSString *const XMPPSINamespace; +extern NSString *const XMPPSIProfileFileTransferNamespace; +extern NSString *const XMPPFeatureNegNamespace; +extern NSString *const XMPPBytestreamsNamespace; +extern NSString *const XMPPIBBNamespace; +extern NSString *const XMPPDiscoItemsNamespace; +extern NSString *const XMPPDiscoInfoNamespace; +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPConstants.m b/Core/XMPPConstants.m new file mode 100644 index 0000000000..d341b56a2b --- /dev/null +++ b/Core/XMPPConstants.m @@ -0,0 +1,10 @@ +#import "XMPPConstants.h" + +NSString *const XMPPSINamespace = @"http://jabber.org/protocol/si"; +NSString *const XMPPSIProfileFileTransferNamespace = + @"http://jabber.org/protocol/si/profile/file-transfer"; +NSString *const XMPPFeatureNegNamespace = @"http://jabber.org/protocol/feature-neg"; +NSString *const XMPPBytestreamsNamespace = @"http://jabber.org/protocol/bytestreams"; +NSString *const XMPPIBBNamespace = @"http://jabber.org/protocol/ibb"; +NSString *const XMPPDiscoItemsNamespace = @"http://jabber.org/protocol/disco#items"; +NSString *const XMPPDiscoInfoNamespace = @"http://jabber.org/protocol/disco#info"; diff --git a/Core/XMPPElement.h b/Core/XMPPElement.h index 3ca2c42c10..98ede34f88 100644 --- a/Core/XMPPElement.h +++ b/Core/XMPPElement.h @@ -1,10 +1,9 @@ #import #import "XMPPJID.h" -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; +NS_ASSUME_NONNULL_BEGIN /** * The XMPPElement provides the base class for XMPPIQ, XMPPMessage & XMPPPresence. @@ -15,17 +14,17 @@ * On the iPhone, the KissXML library provides a drop-in replacement for Apple's NSXML classes. **/ -@interface XMPPElement : NSXMLElement +@interface XMPPElement : NSXMLElement #pragma mark Common Jabber Methods -- (NSString *)elementID; +@property (nonatomic, nullable, readonly) NSString *elementID; -- (XMPPJID *)to; -- (XMPPJID *)from; +@property (nonatomic, nullable, readonly) XMPPJID *to; +@property (nonatomic, nullable, readonly) XMPPJID *from; -- (NSString *)toStr; -- (NSString *)fromStr; +@property (nonatomic, nullable, readonly) NSString *toStr; +@property (nonatomic, nullable, readonly) NSString *fromStr; #pragma mark To and From Methods @@ -42,3 +41,5 @@ - (BOOL)isTo:(XMPPJID *)to from:(XMPPJID *)from options:(XMPPJIDCompareOptions)mask; @end + +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPElement.m b/Core/XMPPElement.m index a9fce77741..55a1113d0a 100644 --- a/Core/XMPPElement.m +++ b/Core/XMPPElement.m @@ -27,7 +27,15 @@ - (id)initWithCoder:(NSCoder *)coder NSString *xmlString; if([coder allowsKeyedCoding]) { - xmlString = [coder decodeObjectForKey:@"xmlString"]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + xmlString = [coder decodeObjectOfClass:[NSString class] forKey:@"xmlString"]; + } + else + { + xmlString = [coder decodeObjectForKey:@"xmlString"]; + } } else { @@ -63,6 +71,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Copying //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPFramework.h b/Core/XMPPFramework.h new file mode 100644 index 0000000000..d51d8aa38a --- /dev/null +++ b/Core/XMPPFramework.h @@ -0,0 +1,176 @@ +#import + +#import "XMPPBandwidthMonitor.h" +#import "XMPP.h" +#import "XMPPConstants.h" +#import "XMPPElement.h" +#import "XMPPInternal.h" +#import "XMPPIQ.h" +#import "XMPPJID.h" +#import "XMPPLogging.h" +#import "XMPPMessage.h" +#import "XMPPModule.h" +#import "XMPPParser.h" +#import "XMPPPresence.h" +#import "XMPPStream.h" +#import "XMPPAnonymousAuthentication.h" +#import "XMPPDeprecatedDigestAuthentication.h" +#import "XMPPDeprecatedPlainAuthentication.h" +#import "XMPPDigestMD5Authentication.h" +#import "XMPPPlainAuthentication.h" +#import "XMPPSCRAMSHA1Authentication.h" +#import "XMPPXOAuth2Google.h" +#import "XMPPCustomBinding.h" +#import "XMPPSASLAuthentication.h" +#import "NSData+XMPP.h" +#import "NSNumber+XMPP.h" +#import "NSXMLElement+XMPP.h" +#import "DDList.h" +#import "GCDMulticastDelegate.h" +#import "RFImageToDataTransformer.h" +#import "XMPPIDTracker.h" +#import "XMPPSRVResolver.h" +#import "XMPPStringPrep.h" +#import "XMPPTimer.h" +#import "XMPPCoreDataStorage.h" +#import "XMPPCoreDataStorageProtected.h" +#import "XMPPDelayedDelivery.h" +#import "NSXMLElement+XEP_0203.h" +#import "XMPPFileTransfer.h" +#import "XMPPIncomingFileTransfer.h" +#import "XMPPOutgoingFileTransfer.h" +#import "XMPPGoogleSharedStatus.h" +#import "NSXMLElement+OMEMO.h" +#import "OMEMOBundle.h" +#import "OMEMOKeyData.h" +#import "OMEMOModule.h" +#import "OMEMOPreKey.h" +#import "OMEMOSignedPreKey.h" +#import "XMPPIQ+OMEMO.h" +#import "XMPPMessage+OMEMO.h" +#import "XMPPProcessOne.h" +#import "XMPPReconnect.h" +#import "XMPPGroupCoreDataStorageObject.h" +#import "XMPPResourceCoreDataStorageObject.h" +#import "XMPPRosterCoreDataStorage.h" +#import "XMPPUserCoreDataStorageObject.h" +#import "XMPPResourceMemoryStorageObject.h" +#import "XMPPRosterMemoryStorage.h" +#import "XMPPRosterMemoryStoragePrivate.h" +#import "XMPPUserMemoryStorageObject.h" +#import "XMPPResource.h" +#import "XMPPRoster.h" +#import "XMPPRosterPrivate.h" +#import "XMPPUser.h" +#import "XMPPIQ+JabberRPC.h" +#import "XMPPIQ+JabberRPCResonse.h" +#import "XMPPJabberRPCModule.h" +#import "XMPPIQ+LastActivity.h" +#import "XMPPLastActivity.h" +#import "XMPPPrivacy.h" +#import "XMPPRoomCoreDataStorage.h" +#import "XMPPRoomMessageCoreDataStorageObject.h" +#import "XMPPRoomOccupantCoreDataStorageObject.h" +#import "XMPPRoomHybridStorage.h" +#import "XMPPRoomHybridStorageProtected.h" +#import "XMPPRoomMessageHybridCoreDataStorageObject.h" +#import "XMPPRoomOccupantHybridMemoryStorageObject.h" +#import "XMPPRoomMemoryStorage.h" +#import "XMPPRoomMessageMemoryStorageObject.h" +#import "XMPPRoomOccupantMemoryStorageObject.h" +#import "XMPPMessage+XEP0045.h" +#import "XMPPMUC.h" +#import "XMPPRoom.h" +#import "XMPPRoomMessage.h" +#import "XMPPRoomOccupant.h" +#import "XMPPRoomPrivate.h" +#import "XMPPvCardAvatarCoreDataStorageObject.h" +#import "XMPPvCardCoreDataStorage.h" +#import "XMPPvCardCoreDataStorageObject.h" +#import "XMPPvCardTempCoreDataStorageObject.h" +#import "XMPPvCardTemp.h" +#import "XMPPvCardTempAdr.h" +#import "XMPPvCardTempAdrTypes.h" +#import "XMPPvCardTempBase.h" +#import "XMPPvCardTempEmail.h" +#import "XMPPvCardTempLabel.h" +#import "XMPPvCardTempModule.h" +#import "XMPPvCardTempTel.h" +#import "XMPPvCardAvatarModule.h" +#import "XMPPDateTimeProfiles.h" +#import "NSDate+XMPPDateTimeProfiles.h" +#import "NSXMLElement+XEP_0059.h" +#import "XMPPResultSet.h" +#import "XMPPIQ+XEP_0060.h" +#import "XMPPPubSub.h" +#import "TURNSocket.h" +#import "XMPPIQ+XEP_0066.h" +#import "XMPPMessage+XEP_0066.h" +#import "XMPPOutOfBandResourceMessaging.h" +#import "XMPPRegistration.h" +#import "NSDate+XMPPDateTimeProfiles.h" +#import "XMPPDateTimeProfiles.h" +#import "XMPPMessage+XEP_0085.h" +#import "XMPPSoftwareVersion.h" +#import "XMPPTransports.h" +#import "NSString+XEP_0106.h" +#import "XMPPCapabilitiesCoreDataStorage.h" +#import "XMPPCapsCoreDataStorageObject.h" +#import "XMPPCapsResourceCoreDataStorageObject.h" +#import "XMPPCapabilities.h" +#import "XMPPMessageArchivingCoreDataStorage.h" +#import "XMPPMessageArchiving_Contact_CoreDataObject.h" +#import "XMPPMessageArchiving_Message_CoreDataObject.h" +#import "XMPPMessageArchiving.h" +#import "XMPPURI.h" +#import "XMPPvCardAvatarModule.h" +#import "NSDate+XMPPDateTimeProfiles.h" +#import "XMPPMessage+XEP_0172.h" +#import "XMPPPresence+XEP_0172.h" +#import "XMPPMessage+XEP_0184.h" +#import "XMPPMessageDeliveryReceipts.h" +#import "XMPPBlocking.h" +#import "XMPPStreamManagementMemoryStorage.h" +#import "XMPPStreamManagementStanzas.h" +#import "XMPPStreamManagement.h" +#import "XMPPManagedMessaging.h" +#import "XMPPAutoPing.h" +#import "XMPPPing.h" +#import "XMPPAutoTime.h" +#import "XMPPTime.h" +#import "NSXMLElement+XEP_0203.h" +#import "XEP_0223.h" +#import "XMPPAttentionModule.h" +#import "XMPPMessage+XEP_0224.h" +#import "XMPPMessage+XEP_0280.h" +#import "XMPPMessageCarbons.h" +#import "NSXMLElement+XEP_0297.h" +#import "NSXMLElement+XEP_0203.h" +#import "XMPPMessage+XEP_0308.h" +#import "XMPPMessageArchiveManagement.h" +#import "XMPPRoomLightCoreDataStorage+XEP_0313.h" +#import "XMPPMessage+XEP_0333.h" +#import "XMPPMessage+XEP_0334.h" +#import "NSXMLElement+XEP_0335.h" +#import "NSXMLElement+XEP_0352.h" +#import "NSXMLElement+XEP_0048.h" +#import "XMPPBookmark.h" +#import "XMPPBookmarksStorageElement.h" +#import "XMPPIQ+XEP_0357.h" +#import "NSXMLElement+XEP_0359.h" +#import "XMPPMessage+XEP_0359.h" +#import "XMPPCapabilities+XEP_0359.h" +#import "XMPPStanzaIdModule.h" +#import "XMPPPushModule.h" +#import "XMPPHTTPFileUpload.h" +#import "XMPPSlot.h" +#import "XMPPMUCLight.h" +#import "XMPPRoomLight.h" +#import "XMPPRoomLightCoreDataStorage.h" +#import "XMPPRoomLightCoreDataStorageProtected.h" +#import "XMPPRoomLightMessageCoreDataStorageObject.h" +#import "XMPPOneToOneChat.h" + + +FOUNDATION_EXPORT double XMPPFrameworkVersionNumber; +FOUNDATION_EXPORT const unsigned char XMPPFrameworkVersionString[]; diff --git a/Core/XMPPIQ.h b/Core/XMPPIQ.h index 4c6380185c..c6765bca94 100644 --- a/Core/XMPPIQ.h +++ b/Core/XMPPIQ.h @@ -11,6 +11,7 @@ * Simply add your own category to XMPPIQ to extend it with your own custom methods. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPIQ : XMPPElement /** @@ -23,26 +24,26 @@ * If the type or elementID parameters are nil, those attributes will not be added. **/ + (XMPPIQ *)iq; -+ (XMPPIQ *)iqWithType:(NSString *)type; -+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid; -+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid; -+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement; -+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid; -+ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement; -+ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type elementID:(nullable NSString *)eid; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; ++ (XMPPIQ *)iqWithType:(nullable NSString *)type child:(nullable NSXMLElement *)childElement; /** * Creates and returns a new XMPPIQ element. * If the type or elementID parameters are nil, those attributes will not be added. **/ -- (id)init; -- (id)initWithType:(NSString *)type; -- (id)initWithType:(NSString *)type to:(XMPPJID *)jid; -- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid; -- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement; -- (id)initWithType:(NSString *)type elementID:(NSString *)eid; -- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement; -- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement; +- (instancetype)init; +- (instancetype)initWithType:(nullable NSString *)type; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; +- (instancetype)initWithType:(nullable NSString *)type elementID:(nullable NSString *)eid; +- (instancetype)initWithType:(nullable NSString *)type elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; +- (instancetype)initWithType:(nullable NSString *)type child:(nullable NSXMLElement *)childElement; /** * Returns the type attribute of the IQ. @@ -51,20 +52,20 @@ * This method converts the attribute to lowercase so * case-sensitive string comparisons are safe (regardless of server treatment). **/ -- (NSString *)type; +@property (nonatomic, readonly, nullable) NSString *type; /** * Convenience methods for determining the IQ type. **/ -- (BOOL)isGetIQ; -- (BOOL)isSetIQ; -- (BOOL)isResultIQ; -- (BOOL)isErrorIQ; +@property (nonatomic, readonly) BOOL isGetIQ; +@property (nonatomic, readonly) BOOL isSetIQ; +@property (nonatomic, readonly) BOOL isResultIQ; +@property (nonatomic, readonly) BOOL isErrorIQ; /** * Convenience method for determining if the IQ is of type 'get' or 'set'. **/ -- (BOOL)requiresResponse; +@property (nonatomic, readonly) BOOL requiresResponse; /** * The XMPP RFC has various rules for the number of child elements an IQ is allowed to have: @@ -77,7 +78,8 @@ * The childElement returns the single non-error element, if one exists, or nil. * The childErrorElement returns the error element, if one exists, or nil. **/ -- (NSXMLElement *)childElement; -- (NSXMLElement *)childErrorElement; +@property (nonatomic, readonly, nullable) NSXMLElement *childElement; +@property (nonatomic, readonly, nullable) NSXMLElement *childErrorElement; @end +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPIQ.m b/Core/XMPPIQ.m index 2edba249ae..832d334575 100644 --- a/Core/XMPPIQ.m +++ b/Core/XMPPIQ.m @@ -116,6 +116,8 @@ - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid if (eid) [self addAttributeWithName:@"id" stringValue:eid]; + else + [self addAttributeWithName:@"id" stringValue:[[NSUUID UUID] UUIDString]]; if (childElement) [self addChild:childElement]; diff --git a/Core/XMPPInternal.h b/Core/XMPPInternal.h index 9b58ae2fdc..1a1bc50c14 100644 --- a/Core/XMPPInternal.h +++ b/Core/XMPPInternal.h @@ -4,6 +4,7 @@ #import "XMPPStream.h" #import "XMPPModule.h" +#import "XMPPParser.h" // Define the various states we'll use to track our progress typedef NS_ENUM(NSInteger, XMPPStreamState) { @@ -22,6 +23,8 @@ typedef NS_ENUM(NSInteger, XMPPStreamState) { STATE_XMPP_CONNECTED, }; +NS_ASSUME_NONNULL_BEGIN + /** * It is recommended that storage classes cache a stream's myJID. * This prevents them from constantly querying the property from the xmppStream instance, @@ -35,7 +38,7 @@ typedef NS_ENUM(NSInteger, XMPPStreamState) { **/ extern NSString *const XMPPStreamDidChangeMyJIDNotification; -@interface XMPPStream (/* Internal */) +@interface XMPPStream (/* Internal */) /** * XMPPStream maintains thread safety by dispatching through the internal serial xmppQueue. @@ -61,6 +64,13 @@ extern NSString *const XMPPStreamDidChangeMyJIDNotification; @property (nonatomic, readonly) dispatch_queue_t xmppQueue; @property (nonatomic, readonly) void *xmppQueueTag; +/** + * Returns the underlying socket for the stream. + * You shouldn't mess with this unless you really + * know what you're doing. + */ +@property (nonatomic, readonly) GCDAsyncSocket *asyncSocket; + /** * Returns the current state of the xmppStream. **/ @@ -101,8 +111,8 @@ extern NSString *const XMPPStreamDidChangeMyJIDNotification; * xmppStream:didSendCustomElement: * xmppStream:didReceiveCustomElement: **/ -- (void)registerCustomElementNames:(NSSet *)names; -- (void)unregisterCustomElementNames:(NSSet *)names; +- (void)registerCustomElementNames:(NSSet *)names; +- (void)unregisterCustomElementNames:(NSSet *)names; @end @@ -113,6 +123,10 @@ extern NSString *const XMPPStreamDidChangeMyJIDNotification; * Normally removing a delegate is a synchronous operation, but due to multiple dispatch_sync operations, * it must occasionally be done asynchronously to avoid deadlock. **/ -- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue synchronously:(BOOL)synchronously; +- (void)removeDelegate:(id)delegate + delegateQueue:(dispatch_queue_t)delegateQueue + synchronously:(BOOL)synchronously; @end + +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPJID.h b/Core/XMPPJID.h index 80ecdff30e..5688c254b9 100644 --- a/Core/XMPPJID.h +++ b/Core/XMPPJID.h @@ -1,31 +1,26 @@ #import -enum XMPPJIDCompareOptions -{ - XMPPJIDCompareUser = 1, // 001 - XMPPJIDCompareDomain = 2, // 010 - XMPPJIDCompareResource = 4, // 100 - - XMPPJIDCompareBare = 3, // 011 - XMPPJIDCompareFull = 7, // 111 +typedef NS_ENUM(NSInteger, XMPPJIDCompare) { + XMPPJIDCompareUser = 1, // 001 + XMPPJIDCompareDomain = 2, // 010 + XMPPJIDCompareResource = 4, // 100 + + XMPPJIDCompareBare = 3, // 011 + XMPPJIDCompareFull = 7, // 111 }; -typedef enum XMPPJIDCompareOptions XMPPJIDCompareOptions; +typedef XMPPJIDCompare XMPPJIDCompareOptions; // for backwards compatibility +NS_ASSUME_NONNULL_BEGIN -@interface XMPPJID : NSObject -{ - __strong NSString *user; - __strong NSString *domain; - __strong NSString *resource; -} +@interface XMPPJID : NSObject -+ (XMPPJID *)jidWithString:(NSString *)jidStr; -+ (XMPPJID *)jidWithString:(NSString *)jidStr resource:(NSString *)resource; -+ (XMPPJID *)jidWithUser:(NSString *)user domain:(NSString *)domain resource:(NSString *)resource; ++ (nullable XMPPJID *)jidWithString:(NSString *)jidStr; ++ (nullable XMPPJID *)jidWithString:(NSString *)jidStr resource:(nullable NSString *)resource; ++ (nullable XMPPJID *)jidWithUser:(nullable NSString *)user domain:(NSString *)domain resource:(nullable NSString *)resource; -@property (strong, readonly) NSString *user; -@property (strong, readonly) NSString *domain; -@property (strong, readonly) NSString *resource; +@property (nonatomic, nullable, copy, readonly) NSString *user; +@property (nonatomic, copy, readonly) NSString *domain; +@property (nonatomic, nullable, copy, readonly) NSString *resource; /** * Terminology (from RFC 6120): @@ -43,22 +38,22 @@ typedef enum XMPPJIDCompareOptions XMPPJIDCompareOptions; * For convenience, there are also methods that that check for a user component as well. **/ -- (XMPPJID *)bareJID; -- (XMPPJID *)domainJID; +@property (nonatomic, readonly) XMPPJID *bareJID NS_SWIFT_NAME(bareJID); +@property (nonatomic, readonly) XMPPJID *domainJID NS_SWIFT_NAME(domainJID); -- (NSString *)bare; -- (NSString *)full; +@property (nonatomic, readonly) NSString *bare; +@property (nonatomic, readonly) NSString *full; -- (BOOL)isBare; -- (BOOL)isBareWithUser; +@property (nonatomic, readonly) BOOL isBare; +@property (nonatomic, readonly) BOOL isBareWithUser; -- (BOOL)isFull; -- (BOOL)isFullWithUser; +@property (nonatomic, readonly) BOOL isFull; +@property (nonatomic, readonly) BOOL isFullWithUser; /** * A server JID does not have a user component. **/ -- (BOOL)isServer; +@property (nonatomic, readonly) BOOL isServer; /** * Returns a new jid with the given resource. @@ -72,3 +67,5 @@ typedef enum XMPPJIDCompareOptions XMPPJIDCompareOptions; - (BOOL)isEqualToJID:(XMPPJID *)aJID options:(XMPPJIDCompareOptions)mask; @end + +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPJID.m b/Core/XMPPJID.m index 8bfbfbd6f2..cdb44f215e 100644 --- a/Core/XMPPJID.m +++ b/Core/XMPPJID.m @@ -7,6 +7,7 @@ @implementation XMPPJID +@synthesize user, domain, resource; + (BOOL)validateDomain:(NSString *)domain { @@ -92,9 +93,19 @@ + (BOOL)parse:(NSString *)jidStr } } - NSString *prepUser = [XMPPStringPrep prepNode:rawUser]; - NSString *prepDomain = [XMPPStringPrep prepDomain:rawDomain]; - NSString *prepResource = [XMPPStringPrep prepResource:rawResource]; + NSString *prepUser = nil; + NSString *prepDomain = nil; + NSString *prepResource = nil; + + if (rawUser) { + prepUser = [XMPPStringPrep prepNode:rawUser]; + } + if (rawDomain) { + prepDomain = [XMPPStringPrep prepDomain:rawDomain]; + } + if (rawResource) { + prepResource = [XMPPStringPrep prepResource:rawResource]; + } if ([XMPPJID validateUser:prepUser domain:prepDomain resource:prepResource]) { @@ -215,9 +226,19 @@ - (id)initWithCoder:(NSCoder *)coder { if ([coder allowsKeyedCoding]) { - user = [[coder decodeObjectForKey:@"user"] copy]; - domain = [[coder decodeObjectForKey:@"domain"] copy]; - resource = [[coder decodeObjectForKey:@"resource"] copy]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + user = [[coder decodeObjectOfClass:[NSString class] forKey:@"user"] copy]; + domain = [[coder decodeObjectOfClass:[NSString class] forKey:@"domain"] copy]; + resource = [[coder decodeObjectOfClass:[NSString class] forKey:@"resource"] copy]; + } + else + { + user = [[coder decodeObjectForKey:@"user"] copy]; + domain = [[coder decodeObjectForKey:@"domain"] copy]; + resource = [[coder decodeObjectForKey:@"resource"] copy]; + } } else { @@ -245,6 +266,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Copying: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Core/XMPPLogging.h b/Core/XMPPLogging.h index c6e8bfab6f..09f8aadf0e 100644 --- a/Core/XMPPLogging.h +++ b/Core/XMPPLogging.h @@ -59,7 +59,7 @@ * If you created your project with a previous version of Xcode, you may need to add the DEBUG macro manually. **/ -#import "DDLog.h" +@import CocoaLumberjack; // Global flag to enable/disable logging throughout the entire xmpp framework. @@ -122,10 +122,10 @@ // These are primarily wrappers around the macros defined in Lumberjack's DDLog.h header file. #define XMPP_LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \ - do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__); } while(0) +do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, nil, sel_getName(_cmd), frmt, ##__VA_ARGS__); } while(0) #define XMPP_LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \ - do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__); } while(0) + do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, nil, __FUNCTION__, frmt, ##__VA_ARGS__); } while(0) #define XMPPLogError(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \ diff --git a/Core/XMPPMessage.h b/Core/XMPPMessage.h index f34e6d3af5..1a8fab415f 100644 --- a/Core/XMPPMessage.h +++ b/Core/XMPPMessage.h @@ -11,45 +11,47 @@ * Simply add your own category to XMPPMessage to extend it with your own custom methods. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage : XMPPElement // Converts an NSXMLElement to an XMPPMessage element in place (no memory allocations or copying) + (XMPPMessage *)messageFromElement:(NSXMLElement *)element; + (XMPPMessage *)message; -+ (XMPPMessage *)messageWithType:(NSString *)type; -+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)to; -+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid; -+ (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement; -+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid; -+ (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement; -+ (XMPPMessage *)messageWithType:(NSString *)type child:(NSXMLElement *)childElement; - -- (id)init; -- (id)initWithType:(NSString *)type; -- (id)initWithType:(NSString *)type to:(XMPPJID *)to; -- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid; -- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement; -- (id)initWithType:(NSString *)type elementID:(NSString *)eid; -- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement; -- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement; - -- (NSString *)type; -- (NSString *)subject; -- (NSString *)body; -- (NSString *)bodyForLanguage:(NSString *)language; -- (NSString *)thread; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type to:(nullable XMPPJID *)to; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type elementID:(nullable NSString *)eid; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; ++ (XMPPMessage *)messageWithType:(nullable NSString *)type child:(nullable NSXMLElement *)childElement; + +- (instancetype)init; +- (instancetype)initWithType:(nullable NSString *)type; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)to; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)jid elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; +- (instancetype)initWithType:(nullable NSString *)type elementID:(nullable NSString *)eid; +- (instancetype)initWithType:(nullable NSString *)type elementID:(nullable NSString *)eid child:(nullable NSXMLElement *)childElement; +- (instancetype)initWithType:(nullable NSString *)type child:(nullable NSXMLElement *)childElement; + +@property (nonatomic, readonly, nullable) NSString *type; +@property (nonatomic, readonly, nullable) NSString *subject; +@property (nonatomic, readonly, nullable) NSString *thread; +@property (nonatomic, readonly, nullable) NSString *body; +- (nullable NSString *)bodyForLanguage:(NSString *)language; - (void)addSubject:(NSString *)subject; - (void)addBody:(NSString *)body; - (void)addBody:(NSString *)body withLanguage:(NSString *)language; - (void)addThread:(NSString *)thread; -- (BOOL)isChatMessage; -- (BOOL)isChatMessageWithBody; -- (BOOL)isErrorMessage; -- (BOOL)isMessageWithBody; +@property (nonatomic, readonly) BOOL isChatMessage; +@property (nonatomic, readonly) BOOL isChatMessageWithBody; +@property (nonatomic, readonly) BOOL isErrorMessage; +@property (nonatomic, readonly) BOOL isMessageWithBody; -- (NSError *)errorMessage; +@property (nonatomic, readonly, nullable) NSError *errorMessage; @end +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPMessage.m b/Core/XMPPMessage.m index 99ab6a751e..2a44aea747 100644 --- a/Core/XMPPMessage.m +++ b/Core/XMPPMessage.m @@ -255,7 +255,7 @@ - (NSError *)errorMessage NSXMLElement *error = [self elementForName:@"error"]; return [NSError errorWithDomain:@"urn:ietf:params:xml:ns:xmpp-stanzas" code:[error attributeIntValueForName:@"code"] - userInfo:[NSDictionary dictionaryWithObject:[error compactXMLString] forKey:NSLocalizedDescriptionKey]]; + userInfo:@{NSLocalizedDescriptionKey : [error compactXMLString]}]; } diff --git a/Core/XMPPModule.h b/Core/XMPPModule.h index 73af16ce1a..f93b5d9c61 100644 --- a/Core/XMPPModule.h +++ b/Core/XMPPModule.h @@ -13,6 +13,7 @@ * The module also automatically registers/unregisters itself with the * xmpp stream during the activate/deactive methods. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPModule : NSObject { XMPPStream *xmppStream; @@ -25,11 +26,12 @@ @property (readonly) dispatch_queue_t moduleQueue; @property (readonly) void *moduleQueueTag; +@property (strong, readonly, nullable) XMPPStream *xmppStream; +@property (nonatomic, readonly) NSString *moduleName; +@property (nonatomic, readonly) id multicastDelegate NS_REFINED_FOR_SWIFT; -@property (strong, readonly) XMPPStream *xmppStream; - -- (id)init; -- (id)initWithDispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue; - (BOOL)activate:(XMPPStream *)aXmppStream; - (void)deactivate; @@ -38,6 +40,37 @@ - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - (void)removeDelegate:(id)delegate; -- (NSString *)moduleName; +@end + +/** + * These helper methods are useful when synchronizing + * external access to properties in XMPPModule subclasses. + */ +@interface XMPPModule(Synchronization) +/** + * Dispatches block synchronously on moduleQueue, or + * executes directly if we're already on the moduleQueue. + * This is most useful for synchronizing external read + * access to properties when writing XMPPModule subclasses. + * + * if (dispatch_get_specific(moduleQueueTag)) + * block(); + * else + * dispatch_sync(moduleQueue, block); + */ +- (void) performBlock:(dispatch_block_t)block NS_REFINED_FOR_SWIFT; +/** + * Dispatches block asynchronously on moduleQueue, or + * executes directly if we're already on the moduleQueue. + * This is most useful for synchronizing external write + * access to properties when writing XMPPModule subclasses. + * + * if (dispatch_get_specific(moduleQueueTag)) + * block(); + * else + * dispatch_async(moduleQueue, block); + */ +- (void) performBlockAsync:(dispatch_block_t)block NS_REFINED_FOR_SWIFT; @end +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPModule.m b/Core/XMPPModule.m index 24ba9d327d..9733366931 100644 --- a/Core/XMPPModule.m +++ b/Core/XMPPModule.m @@ -8,6 +8,7 @@ @implementation XMPPModule +@synthesize multicastDelegate, moduleQueue, xmppStream, moduleQueueTag; /** * Standard init method. @@ -64,16 +65,16 @@ - (BOOL)activate:(XMPPStream *)aXmppStream dispatch_block_t block = ^{ - if (xmppStream != nil) + if (self->xmppStream != nil) { result = NO; } else { - xmppStream = aXmppStream; + self->xmppStream = aXmppStream; - [xmppStream addDelegate:self delegateQueue:moduleQueue]; - [xmppStream registerModule:self]; + [self->xmppStream addDelegate:self delegateQueue:self->moduleQueue]; + [self->xmppStream registerModule:self]; [self didActivate]; } @@ -114,14 +115,14 @@ - (void)deactivate { dispatch_block_t block = ^{ - if (xmppStream) + if (self->xmppStream) { [self willDeactivate]; - [xmppStream removeDelegate:self delegateQueue:moduleQueue]; - [xmppStream unregisterModule:self]; + [self->xmppStream removeDelegate:self delegateQueue:self->moduleQueue]; + [self->xmppStream unregisterModule:self]; - xmppStream = nil; + self->xmppStream = nil; } }; @@ -164,7 +165,7 @@ - (XMPPStream *)xmppStream __block XMPPStream *result; dispatch_sync(moduleQueue, ^{ - result = xmppStream; + result = self->xmppStream; }); return result; @@ -176,7 +177,7 @@ - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue // Asynchronous operation (if outside xmppQueue) dispatch_block_t block = ^{ - [multicastDelegate addDelegate:delegate delegateQueue:delegateQueue]; + [self->multicastDelegate addDelegate:delegate delegateQueue:delegateQueue]; }; if (dispatch_get_specific(moduleQueueTag)) @@ -188,7 +189,7 @@ - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue synchronously:(BOOL)synchronously { dispatch_block_t block = ^{ - [multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue]; + [self->multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue]; }; if (dispatch_get_specific(moduleQueueTag)) @@ -222,3 +223,25 @@ - (NSString *)moduleName } @end + +@implementation XMPPModule(Synchronization) +/** Executes block synchronously on moduleQueue */ +- (void) performBlock:(dispatch_block_t)block { + NSParameterAssert(block); + if (!block) { return; } + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); +} + +/** Executes block asynchronously on moduleQueue */ +- (void) performBlockAsync:(dispatch_block_t)block { + NSParameterAssert(block); + if (!block) { return; } + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} +@end diff --git a/Core/XMPPParser.h b/Core/XMPPParser.h index cff2587eb9..e4afc9fc24 100644 --- a/Core/XMPPParser.h +++ b/Core/XMPPParser.h @@ -1,16 +1,14 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif - - +@import KissXML; +NS_ASSUME_NONNULL_BEGIN +@protocol XMPPParserDelegate; @interface XMPPParser : NSObject -- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)dq; -- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)dq parserQueue:(dispatch_queue_t)pq; +- (instancetype)initWithDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)dq parserQueue:(nullable dispatch_queue_t)pq; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; +- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; /** * Asynchronously parses the given data. @@ -38,3 +36,4 @@ - (void)xmppParserDidParseData:(XMPPParser *)sender; @end +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPParser.m b/Core/XMPPParser.m index d0af6ac419..c480b96553 100644 --- a/Core/XMPPParser.m +++ b/Core/XMPPParser.m @@ -4,7 +4,22 @@ #import #if TARGET_OS_IPHONE - #import "DDXMLPrivate.h" + +// These internal functions are ripped out of DDXMLPrivate.h +// If the function signatures of these methods ever change in KissXML +// we won't know until there are runtime crashes. Not great. + +// This change is needed because DDXMLPrivate.h was made private in the latest +// release of KissXML to avoid Swift issues with non-modular includes. + +@interface DDXMLElement (PrivateAPI) ++ (instancetype)nodeWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner; +@end + +@interface DDXMLNode (PrivateAPI) ++ (void)detachChild:(xmlNodePtr)child andClean:(BOOL)clean andFixNamespaces:(BOOL)fixNamespaces; +@end + #endif #if ! __has_feature(objc_arc) @@ -426,7 +441,7 @@ static void xmpp_xmlAbortDueToMemoryShortage(xmlParserCtxt *ctxt) if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didFail:)]) { NSString *errMsg = @"Unable to allocate memory in xmpp parser"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; NSError *error = [NSError errorWithDomain:@"libxmlErrorDomain" code:1001 userInfo:info]; @@ -777,14 +792,14 @@ - (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQ dispatch_block_t block = ^{ - delegate = newDelegate; + self->delegate = newDelegate; #if !OS_OBJECT_USE_OBJC if (delegateQueue) dispatch_release(delegateQueue); #endif - delegateQueue = newDelegateQueue; + self->delegateQueue = newDelegateQueue; }; if (dispatch_get_specific(xmppParserQueueTag)) @@ -797,15 +812,15 @@ - (void)parseData:(NSData *)data { dispatch_block_t block = ^{ @autoreleasepool { - int result = xmlParseChunk(parserCtxt, (const char *)[data bytes], (int)[data length], 0); + int result = xmlParseChunk(self->parserCtxt, (const char *)[data bytes], (int)[data length], 0); if (result == 0) { - if (delegateQueue && [delegate respondsToSelector:@selector(xmppParserDidParseData:)]) + if (self->delegateQueue && [self->delegate respondsToSelector:@selector(xmppParserDidParseData:)]) { - __strong id theDelegate = delegate; + __strong id theDelegate = self->delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(self->delegateQueue, ^{ @autoreleasepool { [theDelegate xmppParserDidParseData:self]; }}); @@ -813,16 +828,16 @@ - (void)parseData:(NSData *)data } else { - if (delegateQueue && [delegate respondsToSelector:@selector(xmppParser:didFail:)]) + if (self->delegateQueue && [self->delegate respondsToSelector:@selector(xmppParser:didFail:)]) { NSError *error; - xmlError *xmlErr = xmlCtxtGetLastError(parserCtxt); + xmlError *xmlErr = xmlCtxtGetLastError(self->parserCtxt); if (xmlErr->message) { NSString *errMsg = [NSString stringWithFormat:@"%s", xmlErr->message]; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:info]; } @@ -831,9 +846,9 @@ - (void)parseData:(NSData *)data error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:nil]; } - __strong id theDelegate = delegate; + __strong id theDelegate = self->delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(self->delegateQueue, ^{ @autoreleasepool { [theDelegate xmppParser:self didFail:error]; }}); diff --git a/Core/XMPPPresence.h b/Core/XMPPPresence.h index d77554b7ad..35ee31a25b 100644 --- a/Core/XMPPPresence.h +++ b/Core/XMPPPresence.h @@ -1,6 +1,20 @@ #import #import "XMPPElement.h" +// https://xmpp.org/rfcs/rfc6121.html#presence-syntax-children-show +typedef NS_ENUM(NSInteger, XMPPPresenceShow) { + /** Do not disturb */ + XMPPPresenceShowDND, + /** Extended Away */ + XMPPPresenceShowXA, + /** Away */ + XMPPPresenceShowAway, + /** Unrecognized value, or not present */ + XMPPPresenceShowOther, + /** Active and available for chatting */ + XMPPPresenceShowChat +}; + /** * The XMPPPresence class represents a element. * It extends XMPPElement, which in turn extends NSXMLElement. @@ -10,29 +24,34 @@ * This class exists to provide developers an easy way to add functionality to presence processing. * Simply add your own category to XMPPPresence to extend it with your own custom methods. **/ - +NS_ASSUME_NONNULL_BEGIN @interface XMPPPresence : XMPPElement // Converts an NSXMLElement to an XMPPPresence element in place (no memory allocations or copying) + (XMPPPresence *)presenceFromElement:(NSXMLElement *)element; + (XMPPPresence *)presence; -+ (XMPPPresence *)presenceWithType:(NSString *)type; -+ (XMPPPresence *)presenceWithType:(NSString *)type to:(XMPPJID *)to; ++ (XMPPPresence *)presenceWithType:(nullable NSString *)type; ++ (XMPPPresence *)presenceWithType:(nullable NSString *)type to:(nullable XMPPJID *)to; + +- (instancetype)init; +- (instancetype)initWithType:(nullable NSString *)type; +- (instancetype)initWithType:(nullable NSString *)type to:(nullable XMPPJID *)to; -- (id)init; -- (id)initWithType:(NSString *)type; -- (id)initWithType:(NSString *)type to:(XMPPJID *)to; +@property (nonatomic, readonly, nullable) NSString *type; -- (NSString *)type; +@property (nonatomic, readonly, nullable) NSString *show; +@property (nonatomic, readonly, nullable) NSString *status; -- (NSString *)show; -- (NSString *)status; +@property (nonatomic, readonly) NSInteger priority; -- (int)priority; +/** This supercedes the previous intShow method */ +@property (nonatomic, readonly) XMPPPresenceShow showValue; -- (int)intShow; +/** @warn Use showValue instead. This property will be removed in a future release. */ +@property (nonatomic, readonly) NSInteger intShow DEPRECATED_MSG_ATTRIBUTE("Use showValue instead. This property will be removed in a future release."); -- (BOOL)isErrorPresence; +@property (nonatomic, readonly) BOOL isErrorPresence; @end +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPPresence.m b/Core/XMPPPresence.m index a01e46eb9d..fde6500f6a 100644 --- a/Core/XMPPPresence.m +++ b/Core/XMPPPresence.m @@ -116,25 +116,32 @@ - (NSString *)status return [[self elementForName:@"status"] stringValue]; } -- (int)priority +- (NSInteger)priority { return [[[self elementForName:@"priority"] stringValue] intValue]; } -- (int)intShow +- (XMPPPresenceShow)showValue { NSString *show = [self show]; + if (!show) { + return XMPPPresenceShowOther; + } if([show isEqualToString:@"dnd"]) - return 0; + return XMPPPresenceShowDND; if([show isEqualToString:@"xa"]) - return 1; + return XMPPPresenceShowXA; if([show isEqualToString:@"away"]) - return 2; + return XMPPPresenceShowAway; if([show isEqualToString:@"chat"]) - return 4; + return XMPPPresenceShowChat; - return 3; + return XMPPPresenceShowOther; +} + +- (NSInteger) intShow { + return self.showValue; } - (BOOL)isErrorPresence diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 67577ce763..e73e068f16 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -1,12 +1,13 @@ #import #import "XMPPSASLAuthentication.h" #import "XMPPCustomBinding.h" -#import "GCDAsyncSocket.h" #import "GCDMulticastDelegate.h" -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif + +@import KissXML; +@import CocoaAsyncSocket; + +NS_ASSUME_NONNULL_BEGIN @class XMPPSRVResolver; @class XMPPParser; @@ -54,13 +55,13 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * P2P streams using XEP-0174 are also supported. * See the P2P section below. **/ -- (id)init; +- (instancetype)init; /** * Peer to Peer XMPP initialization. * The stream is a direct client to client connection as outlined in XEP-0174. **/ -- (id)initP2PFrom:(XMPPJID *)myJID; +- (instancetype)initP2PFrom:(XMPPJID *)myJID; /** * XMPPStream uses a multicast delegate. @@ -100,7 +101,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * That is, it first do an SRV lookup (as specified in the xmpp RFC). * If that fails, it will fall back to simply attempting to connect to the jid's domain. **/ -@property (readwrite, copy) NSString *hostName; +@property (readwrite, copy, nullable) NSString *hostName; /** * The port the xmpp server is running on. @@ -150,12 +151,12 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * For this reason, you may wish to check the myJID variable after the stream has been connected, * just in case the resource was changed by the server. **/ -@property (readwrite, copy) XMPPJID *myJID; +@property (readwrite, copy, nullable) XMPPJID *myJID; /** * Only used in P2P streams. **/ -@property (strong, readonly) XMPPJID *remoteJID; +@property (strong, readonly, nullable) XMPPJID *remoteJID; /** * Many routers will teardown a socket mapping if there is no activity on the socket. @@ -199,7 +200,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * @see resendMyPresence **/ -@property (strong, readonly) XMPPPresence *myPresence; +@property (strong, readonly, nullable) XMPPPresence *myPresence; /** * Returns the total number of bytes bytes sent/received by the xmpp stream. @@ -233,7 +234,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * The tag property allows you to associate user defined information with the stream. * Tag values are not used internally, and should not be used by xmpp modules. **/ -@property (readwrite, strong) id tag; +@property (readwrite, strong, nullable) id tag; /** * RFC 6121 states that starting a session is no longer required. @@ -269,10 +270,30 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * The default value is NO. **/ -@property (readwrite, assign) BOOL enableBackgroundingOnSocket; +@property (readwrite, assign) BOOL enableBackgroundingOnSocket DEPRECATED_MSG_ATTRIBUTE("Background sockets are no longer available on iOS 10. You must use PushKit and the XEP-0357 module instead."); #endif +/** + * By default, IPv6 is now preferred over IPv4 to satisfy Apple's June 2016 + * DNS64/NAT64 requirements for app approval. Disabling this option may cause + * issues during app approval. + * + * https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html + * + * This new default may cause connectivity issues for misconfigured servers that have + * both A and AAAA DNS records but don't respond to IPv6. A proper solution to this + * is to fix your XMPP server and/or DNS entries. However, when Happy Eyeballs + * (RFC 6555) is implemented upstream in GCDAsyncSocket it should resolve the issue + * of misconfigured servers because it will try both the preferred protocol (IPv6) and + * and fallback protocol (IPv4) after a 300ms delay. + * + * Any changes to this option MUST be done before calling connect. + * + * The default value is YES. + **/ +@property (assign, readwrite) BOOL preferIPv6; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark State //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -281,12 +302,12 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * Returns YES if the connection is closed, and thus no stream is open. * If the stream is neither disconnected, nor connected, then a connection is currently being established. **/ -- (BOOL)isDisconnected; +@property (atomic, readonly) BOOL isDisconnected; /** * Returns YES is the connection is currently connecting **/ -- (BOOL)isConnecting; +@property (atomic, readonly) BOOL isConnecting; /** * Returns YES if the connection is open, and the stream has been properly established. @@ -294,7 +315,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * If this method returns YES, then it is ready for you to start sending and receiving elements. **/ -- (BOOL)isConnected; +@property (atomic, readonly) BOOL isConnected; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Connect & Disconnect @@ -317,7 +338,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * Note: Such servers generally use port 5223 for this, which you will need to set. **/ -- (BOOL)oldSchoolSecureConnectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; +- (BOOL)oldSchoolSecureConnectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr DEPRECATED_MSG_ATTRIBUTE("DO NOT USE. THIS IS DEPRECATED BY THE XMPP SPECIFICATION."); /** * Starts a P2P connection to the given user and given address. @@ -341,6 +362,13 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; **/ - (BOOL)connectP2PWithSocket:(GCDAsyncSocket *)acceptedSocket error:(NSError **)errPtr; +/** + * Aborts any in-progress connection attempt. Has no effect if the stream is already connected or disconnected. + * + * Will dispatch the xmppStreamWasToldToAbortConnect: delegate method. +**/ +- (void)abortConnecting; + /** * Disconnects from the remote host by closing the underlying TCP socket connection. * The terminating element is not sent to the server. @@ -374,7 +402,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * See also the xmppStream:willSecureWithSettings: delegate method. **/ -- (BOOL)isSecure; +@property (atomic, readonly) BOOL isSecure; /** * Returns whether or not the server supports securing the connection via SSL/TLS. @@ -384,7 +412,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * If the connection has already been secured, this method may return NO. **/ -- (BOOL)supportsStartTLS; +@property (atomic, readonly) BOOL supportsStartTLS; /** * Attempts to secure the connection via SSL/TLS. @@ -431,8 +459,8 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * Security Note: * The password will be sent in the clear unless the stream has been secured. **/ -- (BOOL)supportsInBandRegistration; -- (BOOL)registerWithElements:(NSArray *)elements error:(NSError **)errPtr; +@property (atomic, readonly) BOOL supportsInBandRegistration; +- (BOOL)registerWithElements:(NSArray *)elements error:(NSError **)errPtr; - (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -452,7 +480,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * Then this method would return [@"DIGEST-MD5", @"PLAIN"]. **/ -- (NSArray *)supportedAuthenticationMechanisms; +@property (atomic, readonly) NSArray *supportedAuthenticationMechanisms; /** * Returns whether or not the given authentication mechanism name was specified in the @@ -509,17 +537,17 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; /** * Returns whether or not the xmpp stream is currently authenticating with the XMPP Server. **/ -- (BOOL)isAuthenticating; +@property (atomic, readonly) BOOL isAuthenticating; /** * Returns whether or not the xmpp stream has successfully authenticated with the server. **/ -- (BOOL)isAuthenticated; +@property (atomic, readonly) BOOL isAuthenticated; /** * Returns the date when the xmpp stream successfully authenticated with the server. **/ -- (NSDate *)authenticationDate; +@property (atomic, readonly, nullable) NSDate *authenticationDate; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -540,7 +568,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * Then this method would return [@"zlib", @"lzw"]. **/ -- (NSArray *)supportedCompressionMethods; +@property (atomic, readonly) NSArray *supportedCompressionMethods; /** @@ -567,7 +595,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * received during it's connection. This is done for performance reasons and for the obvious benefit * of being more memory efficient. **/ -- (NSXMLElement *)rootElement; +@property (atomic, readonly, nullable) NSXMLElement *rootElement; /** * Returns the version attribute from the servers's element. @@ -615,7 +643,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * Even if you close the xmpp stream after this point, the OS will still do everything it can to send the data. **/ -- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr; +- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; /** * Fetches and resends the myPresence element (if available) in a single atomic operation. @@ -719,8 +747,8 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * This method is most commonly used to generate a unique id value for an xmpp element. **/ -+ (NSString *)generateUUID; -- (NSString *)generateUUID; +@property (nonatomic, readonly) NSString *generateUUID; +@property (nonatomic, class, readonly) NSString *generateUUID; @end @@ -816,7 +844,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * you likely need to add GCDAsyncSocketManuallyEvaluateTrust=YES to the settings. * Then implement the xmppStream:didReceiveTrust:completionHandler: delegate method to perform custom validation. **/ -- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings; +- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary*)settings; /** * Allows a delegate to hook into the TLS handshake and manually validate the peer it's connecting to. @@ -905,7 +933,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * Return nil (or don't implement this method) if you wish to use the standard binding procedure. **/ -- (id )xmppStreamWillBind:(XMPPStream *)sender; +- (nullable id )xmppStreamWillBind:(XMPPStream *)sender; /** * This method is called if the XMPP server doesn't allow our resource of choice @@ -913,7 +941,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * Return an alternative resource or return nil to let the server automatically pick a resource for us. **/ -- (NSString *)xmppStream:(XMPPStream *)sender alternativeResourceForConflictingResource:(NSString *)conflictingResource; +- (nullable NSString *)xmppStream:(XMPPStream *)sender alternativeResourceForConflictingResource:(NSString *)conflictingResource; /** * These methods are called before their respective XML elements are broadcast as received to the rest of the stack. @@ -936,9 +964,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * @see xmppStream:didReceiveMessage: * @see xmppStream:didReceivePresence: **/ -- (XMPPIQ *)xmppStream:(XMPPStream *)sender willReceiveIQ:(XMPPIQ *)iq; -- (XMPPMessage *)xmppStream:(XMPPStream *)sender willReceiveMessage:(XMPPMessage *)message; -- (XMPPPresence *)xmppStream:(XMPPStream *)sender willReceivePresence:(XMPPPresence *)presence; +- (nullable XMPPIQ *)xmppStream:(XMPPStream *)sender willReceiveIQ:(XMPPIQ *)iq; +- (nullable XMPPMessage *)xmppStream:(XMPPStream *)sender willReceiveMessage:(XMPPMessage *)message; +- (nullable XMPPPresence *)xmppStream:(XMPPStream *)sender willReceivePresence:(XMPPPresence *)presence; /** * This method is called if any of the xmppStream:willReceiveX: methods filter the incoming stanza. @@ -996,9 +1024,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * @see xmppStream:didSendMessage: * @see xmppStream:didSendPresence: **/ -- (XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq; -- (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message; -- (XMPPPresence *)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence; +- (nullable XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq; +- (nullable XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message; +- (nullable XMPPPresence *)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence; /** * These methods are called after their respective XML elements are sent over the stream. @@ -1049,10 +1077,15 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; - (void)xmppStreamDidSendClosingStreamStanza:(XMPPStream *)sender; /** - * This methods is called if the XMPP stream's connect times out. + * This method is called if the XMPP stream's connect times out. **/ - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender; +/** + * Invoked when -abortConnecting is called while a connection attempt was in progress. +**/ +- (void)xmppStreamWasToldToAbortConnect:(XMPPStream *)sender; + /** * This method is called after the stream is closed. * @@ -1064,7 +1097,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * * @see xmppStreamConnectDidTimeout: **/ -- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error; +- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(nullable NSError *)error; /** * This method is only used in P2P mode when the connectTo:withAddress: method was used. @@ -1109,3 +1142,5 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; - (void)xmppStream:(XMPPStream *)sender didReceiveCustomElement:(NSXMLElement *)element; @end + +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 3ebe8a3db3..6d11695ade 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -74,7 +74,7 @@ #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -@interface XMPPStream () +@interface XMPPStream () { dispatch_queue_t xmppQueue; void *xmppQueueTag; @@ -111,6 +111,7 @@ @interface XMPPStream () XMPPStreamStartTLSPolicy startTLSPolicy; BOOL skipStartSession; BOOL validatesResponses; + BOOL preferIPv6; id auth; id customBinding; @@ -159,6 +160,7 @@ - (void)signalFailure; @implementation XMPPStream @synthesize tag = userTag; +@synthesize asyncSocket = asyncSocket; /** * Shared initialization between the various init methods. @@ -195,6 +197,7 @@ - (void)commonInit idTracker = [[XMPPIDTracker alloc] initWithStream:self dispatchQueue:xmppQueue]; receipts = [[NSMutableArray alloc] init]; + preferIPv6 = YES; } /** @@ -209,7 +212,7 @@ - (id)init [self commonInit]; // Initialize socket - asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:xmppQueue]; + asyncSocket = [self newSocket]; } return self; } @@ -286,7 +289,7 @@ - (XMPPStreamState)state __block XMPPStreamState result = STATE_XMPP_DISCONNECTED; dispatch_block_t block = ^{ - result = state; + result = self->state; }; if (dispatch_get_specific(xmppQueueTag)) @@ -308,7 +311,7 @@ - (NSString *)hostName __block NSString *result; dispatch_sync(xmppQueue, ^{ - result = hostName; + result = self->hostName; }); return result; @@ -329,7 +332,7 @@ - (void)setHostName:(NSString *)newHostName NSString *newHostNameCopy = [newHostName copy]; dispatch_async(xmppQueue, ^{ - hostName = newHostNameCopy; + self->hostName = newHostNameCopy; }); } @@ -346,7 +349,7 @@ - (UInt16)hostPort __block UInt16 result; dispatch_sync(xmppQueue, ^{ - result = hostPort; + result = self->hostPort; }); return result; @@ -356,7 +359,7 @@ - (UInt16)hostPort - (void)setHostPort:(UInt16)newHostPort { dispatch_block_t block = ^{ - hostPort = newHostPort; + self->hostPort = newHostPort; }; if (dispatch_get_specific(xmppQueueTag)) @@ -370,7 +373,7 @@ - (XMPPStreamStartTLSPolicy)startTLSPolicy __block XMPPStreamStartTLSPolicy result; dispatch_block_t block = ^{ - result = startTLSPolicy; + result = self->startTLSPolicy; }; if (dispatch_get_specific(xmppQueueTag)) @@ -384,7 +387,7 @@ - (XMPPStreamStartTLSPolicy)startTLSPolicy - (void)setStartTLSPolicy:(XMPPStreamStartTLSPolicy)flag { dispatch_block_t block = ^{ - startTLSPolicy = flag; + self->startTLSPolicy = flag; }; if (dispatch_get_specific(xmppQueueTag)) @@ -393,16 +396,40 @@ - (void)setStartTLSPolicy:(XMPPStreamStartTLSPolicy)flag dispatch_async(xmppQueue, block); } +- (void) setPreferIPv6:(BOOL)_preferIPv6 { + dispatch_block_t block = ^{ + self->preferIPv6 = _preferIPv6; + }; + + if (dispatch_get_specific(xmppQueueTag)) + block(); + else + dispatch_async(xmppQueue, block); +} + +- (BOOL) preferIPv6 { + __block BOOL result; + dispatch_block_t block = ^{ + result = self->preferIPv6; + }; + + if (dispatch_get_specific(xmppQueueTag)) + block(); + else + dispatch_sync(xmppQueue, block); + return result; +} + - (XMPPJID *)myJID { __block XMPPJID *result = nil; dispatch_block_t block = ^{ - if (myJID_setByServer) - result = myJID_setByServer; + if (self->myJID_setByServer) + result = self->myJID_setByServer; else - result = myJID_setByClient; + result = self->myJID_setByClient; }; if (dispatch_get_specific(xmppQueueTag)) @@ -419,15 +446,15 @@ - (void)setMyJID_setByClient:(XMPPJID *)newMyJID dispatch_block_t block = ^{ - if (![myJID_setByClient isEqualToJID:newMyJID]) + if (![self->myJID_setByClient isEqualToJID:newMyJID]) { - myJID_setByClient = newMyJID; + self->myJID_setByClient = newMyJID; - if (myJID_setByServer == nil) + if (self->myJID_setByServer == nil) { [[NSNotificationCenter defaultCenter] postNotificationName:XMPPStreamDidChangeMyJIDNotification object:self]; - [multicastDelegate xmppStreamDidChangeMyJID:self]; + [self->multicastDelegate xmppStreamDidChangeMyJID:self]; } } }; @@ -444,21 +471,21 @@ - (void)setMyJID_setByServer:(XMPPJID *)newMyJID dispatch_block_t block = ^{ - if (![myJID_setByServer isEqualToJID:newMyJID]) + if (![self->myJID_setByServer isEqualToJID:newMyJID]) { XMPPJID *oldMyJID; - if (myJID_setByServer) - oldMyJID = myJID_setByServer; + if (self->myJID_setByServer) + oldMyJID = self->myJID_setByServer; else - oldMyJID = myJID_setByClient; + oldMyJID = self->myJID_setByClient; - myJID_setByServer = newMyJID; + self->myJID_setByServer = newMyJID; if (![oldMyJID isEqualToJID:newMyJID]) { [[NSNotificationCenter defaultCenter] postNotificationName:XMPPStreamDidChangeMyJIDNotification object:self]; - [multicastDelegate xmppStreamDidChangeMyJID:self]; + [self->multicastDelegate xmppStreamDidChangeMyJID:self]; } } }; @@ -485,7 +512,7 @@ - (XMPPJID *)remoteJID __block XMPPJID *result; dispatch_sync(xmppQueue, ^{ - result = remoteJID; + result = self->remoteJID; }); return result; @@ -503,7 +530,7 @@ - (XMPPPresence *)myPresence __block XMPPPresence *result; dispatch_sync(xmppQueue, ^{ - result = myPresence; + result = self->myPresence; }); return result; @@ -515,7 +542,7 @@ - (NSTimeInterval)keepAliveInterval __block NSTimeInterval result = 0.0; dispatch_block_t block = ^{ - result = keepAliveInterval; + result = self->keepAliveInterval; }; if (dispatch_get_specific(xmppQueueTag)) @@ -530,12 +557,12 @@ - (void)setKeepAliveInterval:(NSTimeInterval)interval { dispatch_block_t block = ^{ - if (keepAliveInterval != interval) + if (self->keepAliveInterval != interval) { if (interval <= 0.0) - keepAliveInterval = interval; + self->keepAliveInterval = interval; else - keepAliveInterval = MAX(interval, MIN_KEEPALIVE_INTERVAL); + self->keepAliveInterval = MAX(interval, MIN_KEEPALIVE_INTERVAL); [self setupKeepAliveTimer]; } @@ -553,7 +580,7 @@ - (char)keepAliveWhitespaceCharacter dispatch_block_t block = ^{ - NSString *keepAliveString = [[NSString alloc] initWithData:keepAliveData encoding:NSUTF8StringEncoding]; + NSString *keepAliveString = [[NSString alloc] initWithData:self->keepAliveData encoding:NSUTF8StringEncoding]; if ([keepAliveString length] > 0) { keepAliveChar = (char)[keepAliveString characterAtIndex:0]; @@ -574,7 +601,7 @@ - (void)setKeepAliveWhitespaceCharacter:(char)keepAliveChar if (keepAliveChar == ' ' || keepAliveChar == '\n' || keepAliveChar == '\t') { - keepAliveData = [[NSString stringWithFormat:@"%c", keepAliveChar] dataUsingEncoding:NSUTF8StringEncoding]; + self->keepAliveData = [[NSString stringWithFormat:@"%c", keepAliveChar] dataUsingEncoding:NSUTF8StringEncoding]; } else { @@ -593,7 +620,7 @@ - (uint64_t)numberOfBytesSent __block uint64_t result = 0; dispatch_block_t block = ^{ - result = numberOfBytesSent; + result = self->numberOfBytesSent; }; if (dispatch_get_specific(xmppQueueTag)) @@ -609,7 +636,7 @@ - (uint64_t)numberOfBytesReceived __block uint64_t result = 0; dispatch_block_t block = ^{ - result = numberOfBytesReceived; + result = self->numberOfBytesReceived; }; if (dispatch_get_specific(xmppQueueTag)) @@ -626,8 +653,8 @@ - (void)getNumberOfBytesSent:(uint64_t *)bytesSentPtr numberOfBytesReceived:(uin __block uint64_t bytesReceived = 0; dispatch_block_t block = ^{ - bytesSent = numberOfBytesSent; - bytesReceived = numberOfBytesReceived; + bytesSent = self->numberOfBytesSent; + bytesReceived = self->numberOfBytesReceived; }; if (dispatch_get_specific(xmppQueueTag)) @@ -644,7 +671,7 @@ - (BOOL)resetByteCountPerConnection __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kResetByteCountPerConnection) ? YES : NO; + result = (self->config & kResetByteCountPerConnection) ? YES : NO; }; if (dispatch_get_specific(xmppQueueTag)) @@ -659,9 +686,9 @@ - (void)setResetByteCountPerConnection:(BOOL)flag { dispatch_block_t block = ^{ if (flag) - config |= kResetByteCountPerConnection; + self->config |= kResetByteCountPerConnection; else - config &= ~kResetByteCountPerConnection; + self->config &= ~kResetByteCountPerConnection; }; if (dispatch_get_specific(xmppQueueTag)) @@ -675,7 +702,7 @@ - (BOOL)skipStartSession __block BOOL result = NO; dispatch_block_t block = ^{ - result = skipStartSession; + result = self->skipStartSession; }; if (dispatch_get_specific(xmppQueueTag)) @@ -689,7 +716,7 @@ - (BOOL)skipStartSession - (void)setSkipStartSession:(BOOL)flag { dispatch_block_t block = ^{ - skipStartSession = flag; + self->skipStartSession = flag; }; if (dispatch_get_specific(xmppQueueTag)) @@ -703,7 +730,7 @@ - (BOOL)validatesResponses __block BOOL result = NO; dispatch_block_t block = ^{ - result = validatesResponses; + result = self->validatesResponses; }; if (dispatch_get_specific(xmppQueueTag)) @@ -717,7 +744,7 @@ - (BOOL)validatesResponses - (void)setValidatesResponses:(BOOL)flag { dispatch_block_t block = ^{ - validatesResponses = flag; + self->validatesResponses = flag; }; if (dispatch_get_specific(xmppQueueTag)) @@ -733,7 +760,7 @@ - (BOOL)enableBackgroundingOnSocket __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kEnableBackgroundingOnSocket) ? YES : NO; + result = (self->config & kEnableBackgroundingOnSocket) ? YES : NO; }; if (dispatch_get_specific(xmppQueueTag)) @@ -748,9 +775,9 @@ - (void)setEnableBackgroundingOnSocket:(BOOL)flag { dispatch_block_t block = ^{ if (flag) - config |= kEnableBackgroundingOnSocket; + self->config |= kEnableBackgroundingOnSocket; else - config &= ~kEnableBackgroundingOnSocket; + self->config &= ~kEnableBackgroundingOnSocket; }; if (dispatch_get_specific(xmppQueueTag)) @@ -770,7 +797,7 @@ - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue // Asynchronous operation (if outside xmppQueue) dispatch_block_t block = ^{ - [multicastDelegate addDelegate:delegate delegateQueue:delegateQueue]; + [self->multicastDelegate addDelegate:delegate delegateQueue:delegateQueue]; }; if (dispatch_get_specific(xmppQueueTag)) @@ -784,7 +811,7 @@ - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueu // Synchronous operation dispatch_block_t block = ^{ - [multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue]; + [self->multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue]; }; if (dispatch_get_specific(xmppQueueTag)) @@ -798,7 +825,7 @@ - (void)removeDelegate:(id)delegate // Synchronous operation dispatch_block_t block = ^{ - [multicastDelegate removeDelegate:delegate]; + [self->multicastDelegate removeDelegate:delegate]; }; if (dispatch_get_specific(xmppQueueTag)) @@ -822,7 +849,7 @@ - (BOOL)isP2P __block BOOL result; dispatch_sync(xmppQueue, ^{ - result = (config & kP2PMode) ? YES : NO; + result = (self->config & kP2PMode) ? YES : NO; }); return result; @@ -840,7 +867,7 @@ - (BOOL)isP2PInitiator __block BOOL result; dispatch_sync(xmppQueue, ^{ - result = ((config & kP2PMode) && (flags & kP2PInitiator)); + result = ((self->config & kP2PMode) && (self->flags & kP2PInitiator)); }); return result; @@ -858,7 +885,7 @@ - (BOOL)isP2PRecipient __block BOOL result; dispatch_sync(xmppQueue, ^{ - result = ((config & kP2PMode) && !(flags & kP2PInitiator)); + result = ((self->config & kP2PMode) && !(self->flags & kP2PInitiator)); }); return result; @@ -895,7 +922,7 @@ - (BOOL)isDisconnected __block BOOL result = NO; dispatch_block_t block = ^{ - result = (state == STATE_XMPP_DISCONNECTED); + result = (self->state == STATE_XMPP_DISCONNECTED); }; if (dispatch_get_specific(xmppQueueTag)) @@ -917,7 +944,7 @@ - (BOOL)isConnecting __block BOOL result = NO; dispatch_block_t block = ^{ @autoreleasepool { - result = (state == STATE_XMPP_CONNECTING); + result = (self->state == STATE_XMPP_CONNECTING); }}; if (dispatch_get_specific(xmppQueueTag)) @@ -936,7 +963,7 @@ - (BOOL)isConnected __block BOOL result = NO; dispatch_block_t block = ^{ - result = (state == STATE_XMPP_CONNECTED); + result = (self->state == STATE_XMPP_CONNECTED); }; if (dispatch_get_specific(xmppQueueTag)) @@ -1061,10 +1088,10 @@ - (BOOL)connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_DISCONNECTED) + if (self->state != STATE_XMPP_DISCONNECTED) { NSString *errMsg = @"Attempting to connect while already connected or connecting."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1075,7 +1102,7 @@ - (BOOL)connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr if ([self isP2P]) { NSString *errMsg = @"P2P streams must use either connectTo:withAddress: or connectP2PWithSocket:."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidType userInfo:info]; @@ -1083,7 +1110,7 @@ - (BOOL)connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr return_from_block; } - if (myJID_setByClient == nil) + if (self->myJID_setByClient == nil) { // Note: If you wish to use anonymous authentication, you should still set myJID prior to calling connect. // You can simply set it to something like "anonymous@", where "" is the proper domain. @@ -1100,7 +1127,7 @@ - (BOOL)connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr // but the xmpp handshake requires the xmpp domain (testing.mycompany.com). NSString *errMsg = @"You must set myJID before calling connect."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidProperty userInfo:info]; @@ -1109,22 +1136,22 @@ - (BOOL)connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr } // Notify delegates - [multicastDelegate xmppStreamWillConnect:self]; + [self->multicastDelegate xmppStreamWillConnect:self]; - if ([hostName length] == 0) + if ([self->hostName length] == 0) { // Resolve the hostName via myJID SRV resolution - state = STATE_XMPP_RESOLVING_SRV; + self->state = STATE_XMPP_RESOLVING_SRV; - srvResolver = [[XMPPSRVResolver alloc] initWithdDelegate:self delegateQueue:xmppQueue resolverQueue:NULL]; + self->srvResolver = [[XMPPSRVResolver alloc] initWithDelegate:self delegateQueue:self->xmppQueue resolverQueue:NULL]; - srvResults = nil; - srvResultsIndex = 0; + self->srvResults = nil; + self->srvResultsIndex = 0; - NSString *srvName = [XMPPSRVResolver srvNameFromXMPPDomain:[myJID_setByClient domain]]; + NSString *srvName = [XMPPSRVResolver srvNameFromXMPPDomain:[self->myJID_setByClient domain]]; - [srvResolver startWithSRVName:srvName timeout:TIMEOUT_SRV_RESOLUTION]; + [self->srvResolver startWithSRVName:srvName timeout:TIMEOUT_SRV_RESOLUTION]; result = YES; } @@ -1132,15 +1159,15 @@ - (BOOL)connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr { // Open TCP connection to the configured hostName. - state = STATE_XMPP_CONNECTING; + self->state = STATE_XMPP_CONNECTING; NSError *connectErr = nil; - result = [self connectToHost:hostName onPort:hostPort withTimeout:XMPPStreamTimeoutNone error:&connectErr]; + result = [self connectToHost:self->hostName onPort:self->hostPort withTimeout:XMPPStreamTimeoutNone error:&connectErr]; if (!result) { err = connectErr; - state = STATE_XMPP_DISCONNECTED; + self->state = STATE_XMPP_DISCONNECTED; } } @@ -1218,10 +1245,10 @@ - (BOOL)connectTo:(XMPPJID *)jid withAddress:(NSData *)remoteAddr withTimeout:(N dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_DISCONNECTED) + if (self->state != STATE_XMPP_DISCONNECTED) { NSString *errMsg = @"Attempting to connect while already connected or connecting."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1232,7 +1259,7 @@ - (BOOL)connectTo:(XMPPJID *)jid withAddress:(NSData *)remoteAddr withTimeout:(N if (![self isP2P]) { NSString *errMsg = @"Non P2P streams must use the connect: method"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidType userInfo:info]; @@ -1241,34 +1268,34 @@ - (BOOL)connectTo:(XMPPJID *)jid withAddress:(NSData *)remoteAddr withTimeout:(N } // Turn on P2P initiator flag - flags |= kP2PInitiator; + self->flags |= kP2PInitiator; // Store remoteJID - remoteJID = [jid copy]; + self->remoteJID = [jid copy]; - NSAssert((asyncSocket == nil), @"Forgot to release the previous asyncSocket instance."); + NSAssert((self->asyncSocket == nil), @"Forgot to release the previous asyncSocket instance."); // Notify delegates - [multicastDelegate xmppStreamWillConnect:self]; + [self->multicastDelegate xmppStreamWillConnect:self]; // Update state - state = STATE_XMPP_CONNECTING; + self->state = STATE_XMPP_CONNECTING; // Initailize socket - asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:xmppQueue]; + self->asyncSocket = [self newSocket]; NSError *connectErr = nil; - result = [asyncSocket connectToAddress:remoteAddr error:&connectErr]; + result = [self->asyncSocket connectToAddress:remoteAddr error:&connectErr]; if (result == NO) { err = connectErr; - state = STATE_XMPP_DISCONNECTED; + self->state = STATE_XMPP_DISCONNECTED; } else if ([self resetByteCountPerConnection]) { - numberOfBytesSent = 0; - numberOfBytesReceived = 0; + self->numberOfBytesSent = 0; + self->numberOfBytesReceived = 0; } if(result) @@ -1304,10 +1331,10 @@ - (BOOL)connectP2PWithSocket:(GCDAsyncSocket *)acceptedSocket error:(NSError **) dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_DISCONNECTED) + if (self->state != STATE_XMPP_DISCONNECTED) { NSString *errMsg = @"Attempting to connect while already connected or connecting."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1318,7 +1345,7 @@ - (BOOL)connectP2PWithSocket:(GCDAsyncSocket *)acceptedSocket error:(NSError **) if (![self isP2P]) { NSString *errMsg = @"Non P2P streams must use the connect: method"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidType userInfo:info]; @@ -1329,7 +1356,7 @@ - (BOOL)connectP2PWithSocket:(GCDAsyncSocket *)acceptedSocket error:(NSError **) if (acceptedSocket == nil) { NSString *errMsg = @"Parameter acceptedSocket is nil."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidParameter userInfo:info]; @@ -1338,24 +1365,24 @@ - (BOOL)connectP2PWithSocket:(GCDAsyncSocket *)acceptedSocket error:(NSError **) } // Turn off P2P initiator flag - flags &= ~kP2PInitiator; + self->flags &= ~kP2PInitiator; - NSAssert((asyncSocket == nil), @"Forgot to release the previous asyncSocket instance."); + NSAssert((self->asyncSocket == nil), @"Forgot to release the previous asyncSocket instance."); // Store and configure socket - asyncSocket = acceptedSocket; - [asyncSocket setDelegate:self delegateQueue:xmppQueue]; + self->asyncSocket = acceptedSocket; + [self->asyncSocket setDelegate:self delegateQueue:self->xmppQueue]; // Notify delegates - [multicastDelegate xmppStream:self socketDidConnect:asyncSocket]; + [self->multicastDelegate xmppStream:self socketDidConnect:self->asyncSocket]; // Update state - state = STATE_XMPP_CONNECTING; + self->state = STATE_XMPP_CONNECTING; if ([self resetByteCountPerConnection]) { - numberOfBytesSent = 0; - numberOfBytesReceived = 0; + self->numberOfBytesSent = 0; + self->numberOfBytesReceived = 0; } // Start the XML stream @@ -1374,9 +1401,43 @@ - (BOOL)connectP2PWithSocket:(GCDAsyncSocket *)acceptedSocket error:(NSError **) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnect +#pragma mark Abort/Disconnect //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)abortConnecting +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ @autoreleasepool { + + [self endConnectTimeout]; + + if (self->state != STATE_XMPP_DISCONNECTED && self->state != STATE_XMPP_CONNECTED) + { + [self->multicastDelegate xmppStreamWasToldToAbortConnect:self]; + + if (self->state == STATE_XMPP_RESOLVING_SRV) + { + [self->srvResolver stop]; + self->srvResolver = nil; + + self->state = STATE_XMPP_DISCONNECTED; + } + else + { + [self->asyncSocket disconnect]; + + // Everthing will be handled in socketDidDisconnect:withError: + } + } + }}; + + if (dispatch_get_specific(xmppQueueTag)) + block(); + else + dispatch_sync(xmppQueue, block); +} + /** * Closes the connection to the remote host. **/ @@ -1386,22 +1447,22 @@ - (void)disconnect dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_DISCONNECTED) + if (self->state != STATE_XMPP_DISCONNECTED) { - [multicastDelegate xmppStreamWasToldToDisconnect:self]; + [self->multicastDelegate xmppStreamWasToldToDisconnect:self]; - if (state == STATE_XMPP_RESOLVING_SRV) + if (self->state == STATE_XMPP_RESOLVING_SRV) { - [srvResolver stop]; - srvResolver = nil; + [self->srvResolver stop]; + self->srvResolver = nil; - state = STATE_XMPP_DISCONNECTED; + self->state = STATE_XMPP_DISCONNECTED; - [multicastDelegate xmppStreamDidDisconnect:self withError:nil]; + [self->multicastDelegate xmppStreamDidDisconnect:self withError:nil]; } else { - [asyncSocket disconnect]; + [self->asyncSocket disconnect]; // Everthing will be handled in socketDidDisconnect:withError: } @@ -1420,18 +1481,18 @@ - (void)disconnectAfterSending dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_DISCONNECTED) + if (self->state != STATE_XMPP_DISCONNECTED) { - [multicastDelegate xmppStreamWasToldToDisconnect:self]; + [self->multicastDelegate xmppStreamWasToldToDisconnect:self]; - if (state == STATE_XMPP_RESOLVING_SRV) + if (self->state == STATE_XMPP_RESOLVING_SRV) { - [srvResolver stop]; - srvResolver = nil; + [self->srvResolver stop]; + self->srvResolver = nil; - state = STATE_XMPP_DISCONNECTED; + self->state = STATE_XMPP_DISCONNECTED; - [multicastDelegate xmppStreamDidDisconnect:self withError:nil]; + [self->multicastDelegate xmppStreamDidDisconnect:self withError:nil]; } else { @@ -1439,10 +1500,10 @@ - (void)disconnectAfterSending NSData *termData = [termStr dataUsingEncoding:NSUTF8StringEncoding]; XMPPLogSend(@"SEND: %@", termStr); - numberOfBytesSent += [termData length]; + self->numberOfBytesSent += [termData length]; - [asyncSocket writeData:termData withTimeout:TIMEOUT_XMPP_WRITE tag:TAG_XMPP_WRITE_STOP]; - [asyncSocket disconnectAfterWriting]; + [self->asyncSocket writeData:termData withTimeout:TIMEOUT_XMPP_WRITE tag:TAG_XMPP_WRITE_STOP]; + [self->asyncSocket disconnectAfterWriting]; // Everthing will be handled in socketDidDisconnect:withError: } @@ -1474,7 +1535,7 @@ - (BOOL)isSecure __block BOOL result; dispatch_sync(xmppQueue, ^{ - result = (flags & kIsSecure) ? YES : NO; + result = (self->flags & kIsSecure) ? YES : NO; }); return result; @@ -1485,9 +1546,9 @@ - (void)setIsSecure:(BOOL)flag { dispatch_block_t block = ^{ if(flag) - flags |= kIsSecure; + self->flags |= kIsSecure; else - flags &= ~kIsSecure; + self->flags &= ~kIsSecure; }; if (dispatch_get_specific(xmppQueueTag)) @@ -1504,9 +1565,9 @@ - (BOOL)supportsStartTLS // The root element can be properly queried for authentication mechanisms anytime after the // stream:features are received, and TLS has been setup (if required) - if (state >= STATE_XMPP_POST_NEGOTIATION) + if (self->state >= STATE_XMPP_POST_NEGOTIATION) { - NSXMLElement *features = [rootElement elementForName:@"stream:features"]; + NSXMLElement *features = [self->rootElement elementForName:@"stream:features"]; NSXMLElement *starttls = [features elementForName:@"starttls" xmlns:@"urn:ietf:params:xml:ns:xmpp-tls"]; result = (starttls != nil); @@ -1548,10 +1609,10 @@ - (BOOL)secureConnection:(NSError **)errPtr dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_CONNECTED) + if (self->state != STATE_XMPP_CONNECTED) { NSString *errMsg = @"Please wait until the stream is connected."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1562,7 +1623,7 @@ - (BOOL)secureConnection:(NSError **)errPtr if ([self isSecure]) { NSString *errMsg = @"The connection is already secure."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1573,7 +1634,7 @@ - (BOOL)secureConnection:(NSError **)errPtr if (![self supportsStartTLS]) { NSString *errMsg = @"The server does not support startTLS."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; @@ -1582,7 +1643,7 @@ - (BOOL)secureConnection:(NSError **)errPtr } // Update state - state = STATE_XMPP_STARTTLS_1; + self->state = STATE_XMPP_STARTTLS_1; // Send the startTLS XML request [self sendStartTLSRequest]; @@ -1620,9 +1681,9 @@ - (BOOL)supportsInBandRegistration // The root element can be properly queried for authentication mechanisms anytime after the // stream:features are received, and TLS has been setup (if required) - if (state >= STATE_XMPP_POST_NEGOTIATION) + if (self->state >= STATE_XMPP_POST_NEGOTIATION) { - NSXMLElement *features = [rootElement elementForName:@"stream:features"]; + NSXMLElement *features = [self->rootElement elementForName:@"stream:features"]; NSXMLElement *reg = [features elementForName:@"register" xmlns:@"http://jabber.org/features/iq-register"]; result = (reg != nil); @@ -1652,10 +1713,10 @@ - (BOOL)registerWithElements:(NSArray *)elements error:(NSError **)errPtr dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_CONNECTED) + if (self->state != STATE_XMPP_CONNECTED) { NSString *errMsg = @"Please wait until the stream is connected."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1666,7 +1727,7 @@ - (BOOL)registerWithElements:(NSArray *)elements error:(NSError **)errPtr if (![self supportsInBandRegistration]) { NSString *errMsg = @"The server does not support in band registration."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; @@ -1688,14 +1749,14 @@ - (BOOL)registerWithElements:(NSArray *)elements error:(NSError **)errPtr NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; XMPPLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; + self->numberOfBytesSent += [outgoingData length]; - [asyncSocket writeData:outgoingData + [self->asyncSocket writeData:outgoingData withTimeout:TIMEOUT_XMPP_WRITE tag:TAG_XMPP_WRITE_STREAM]; // Update state - state = STATE_XMPP_REGISTERING; + self->state = STATE_XMPP_REGISTERING; }}; @@ -1717,7 +1778,7 @@ - (BOOL)registerWithElements:(NSArray *)elements error:(NSError **)errPtr * * If the XMPPStream is not connected, or the server doesn't support in-band registration, this method does nothing. **/ -- (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr +- (BOOL)registerWithPassword:(NSString *)password error:(NSError * __autoreleasing *)errPtr { XMPPLogTrace(); @@ -1726,10 +1787,10 @@ - (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr dispatch_block_t block = ^{ @autoreleasepool { - if (myJID_setByClient == nil) + if (self->myJID_setByClient == nil) { NSString *errMsg = @"You must set myJID before calling registerWithPassword:error:."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidProperty userInfo:info]; @@ -1737,7 +1798,7 @@ - (BOOL)registerWithPassword:(NSString *)password error:(NSError **)errPtr return_from_block; } - NSString *username = [myJID_setByClient user]; + NSString *username = [self->myJID_setByClient user]; NSMutableArray *elements = [NSMutableArray array]; [elements addObject:[NSXMLElement elementWithName:@"username" stringValue:username]]; @@ -1771,9 +1832,9 @@ - (NSArray *)supportedAuthenticationMechanisms // The root element can be properly queried for authentication mechanisms anytime after the // stream:features are received, and TLS has been setup (if required). - if (state >= STATE_XMPP_POST_NEGOTIATION) + if (self->state >= STATE_XMPP_POST_NEGOTIATION) { - NSXMLElement *features = [rootElement elementForName:@"stream:features"]; + NSXMLElement *features = [self->rootElement elementForName:@"stream:features"]; NSXMLElement *mech = [features elementForName:@"mechanisms" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; NSArray *mechanisms = [mech elementsForName:@"mechanism"]; @@ -1808,9 +1869,9 @@ - (BOOL)supportsAuthenticationMechanism:(NSString *)mechanismType // The root element can be properly queried for authentication mechanisms anytime after the // stream:features are received, and TLS has been setup (if required). - if (state >= STATE_XMPP_POST_NEGOTIATION) + if (self->state >= STATE_XMPP_POST_NEGOTIATION) { - NSXMLElement *features = [rootElement elementForName:@"stream:features"]; + NSXMLElement *features = [self->rootElement elementForName:@"stream:features"]; NSXMLElement *mech = [features elementForName:@"mechanisms" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"]; NSArray *mechanisms = [mech elementsForName:@"mechanism"]; @@ -1843,10 +1904,10 @@ - (BOOL)authenticate:(id )inAuth error:(NSError **)errPt dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_CONNECTED) + if (self->state != STATE_XMPP_CONNECTED) { NSString *errMsg = @"Please wait until the stream is connected."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1854,10 +1915,10 @@ - (BOOL)authenticate:(id )inAuth error:(NSError **)errPt return_from_block; } - if (myJID_setByClient == nil) + if (self->myJID_setByClient == nil) { NSString *errMsg = @"You must set myJID before calling authenticate:error:."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidProperty userInfo:info]; @@ -1868,18 +1929,18 @@ - (BOOL)authenticate:(id )inAuth error:(NSError **)errPt // Change state. // We do this now because when we invoke the start method below, // it may in turn invoke our sendAuthElement method, which expects us to be in STATE_XMPP_AUTH. - state = STATE_XMPP_AUTH; + self->state = STATE_XMPP_AUTH; if ([inAuth start:&err]) { - auth = inAuth; + self->auth = inAuth; result = YES; } else { // Unable to start authentication for some reason. // Revert back to connected state. - state = STATE_XMPP_CONNECTED; + self->state = STATE_XMPP_CONNECTED; } }}; @@ -1915,10 +1976,10 @@ - (BOOL)authenticateWithPassword:(NSString *)inPassword error:(NSError **)errPtr dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_CONNECTED) + if (self->state != STATE_XMPP_CONNECTED) { NSString *errMsg = @"Please wait until the stream is connected."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -1926,10 +1987,10 @@ - (BOOL)authenticateWithPassword:(NSString *)inPassword error:(NSError **)errPtr return_from_block; } - if (myJID_setByClient == nil) + if (self->myJID_setByClient == nil) { NSString *errMsg = @"You must set myJID before calling authenticate:error:."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidProperty userInfo:info]; @@ -1971,7 +2032,7 @@ - (BOOL)authenticateWithPassword:(NSString *)inPassword error:(NSError **)errPtr else { NSString *errMsg = @"No suitable authentication method found"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; @@ -1996,7 +2057,7 @@ - (BOOL)isAuthenticating __block BOOL result = NO; dispatch_block_t block = ^{ @autoreleasepool { - result = (state == STATE_XMPP_AUTH); + result = (self->state == STATE_XMPP_AUTH); }}; if (dispatch_get_specific(xmppQueueTag)) @@ -2012,7 +2073,7 @@ - (BOOL)isAuthenticated __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kIsAuthenticated) ? YES : NO; + result = (self->flags & kIsAuthenticated) ? YES : NO; }; if (dispatch_get_specific(xmppQueueTag)) @@ -2028,13 +2089,13 @@ - (void)setIsAuthenticated:(BOOL)flag dispatch_block_t block = ^{ if(flag) { - flags |= kIsAuthenticated; - authenticationDate = [NSDate date]; + self->flags |= kIsAuthenticated; + self->authenticationDate = [NSDate date]; } else { - flags &= ~kIsAuthenticated; - authenticationDate = nil; + self->flags &= ~kIsAuthenticated; + self->authenticationDate = nil; } }; @@ -2049,9 +2110,9 @@ - (NSDate *)authenticationDate __block NSDate *result = nil; dispatch_block_t block = ^{ - if(flags & kIsAuthenticated) + if(self->flags & kIsAuthenticated) { - result = authenticationDate; + result = self->authenticationDate; } }; @@ -2076,9 +2137,9 @@ - (NSArray *)supportedCompressionMethods // The root element can be properly queried for compression methods anytime after the // stream:features are received, and TLS has been setup (if required). - if (state >= STATE_XMPP_POST_NEGOTIATION) + if (self->state >= STATE_XMPP_POST_NEGOTIATION) { - NSXMLElement *features = [rootElement elementForName:@"stream:features"]; + NSXMLElement *features = [self->rootElement elementForName:@"stream:features"]; NSXMLElement *compression = [features elementForName:@"compression" xmlns:@"http://jabber.org/features/compress"]; NSArray *methods = [compression elementsForName:@"method"]; @@ -2113,9 +2174,9 @@ - (BOOL)supportsCompressionMethod:(NSString *)compressionMethod // The root element can be properly queried for compression methods anytime after the // stream:features are received, and TLS has been setup (if required). - if (state >= STATE_XMPP_POST_NEGOTIATION) + if (self->state >= STATE_XMPP_POST_NEGOTIATION) { - NSXMLElement *features = [rootElement elementForName:@"stream:features"]; + NSXMLElement *features = [self->rootElement elementForName:@"stream:features"]; NSXMLElement *compression = [features elementForName:@"compression" xmlns:@"http://jabber.org/features/compress"]; NSArray *methods = [compression elementsForName:@"method"]; @@ -2162,7 +2223,7 @@ - (NSXMLElement *)rootElement __block NSXMLElement *result = nil; dispatch_sync(xmppQueue, ^{ - result = [rootElement copy]; + result = [self->rootElement copy]; }); return result; @@ -2185,7 +2246,7 @@ - (float)serverXmppStreamVersionNumber __block float result; dispatch_sync(xmppQueue, ^{ - result = [rootElement attributeFloatValueForName:@"version" withDefaultValue:0.0F]; + result = [self->rootElement attributeFloatValueForName:@"version" withDefaultValue:0.0F]; }); return result; @@ -2251,9 +2312,9 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag if (modifiedIQ) { - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) { + if (self->state == STATE_XMPP_CONNECTED) { [self continueSendIQ:modifiedIQ withTag:tag]; } else { [self failToSendIQ:modifiedIQ]; @@ -2323,9 +2384,9 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag if (modifiedMessage) { - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) { + if (self->state == STATE_XMPP_CONNECTED) { [self continueSendMessage:modifiedMessage withTag:tag]; } else { @@ -2396,9 +2457,9 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag if (modifiedPresence) { - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) { + if (self->state == STATE_XMPP_CONNECTED) { [self continueSendPresence:modifiedPresence withTag:tag]; } else { [self failToSendPresence:modifiedPresence]; @@ -2544,7 +2605,7 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag } /** - * This methods handles sending an XML stanza. + * This method handles sending an XML stanza. * If the XMPPStream is not connected, this method does nothing. **/ - (void)sendElement:(NSXMLElement *)element @@ -2553,7 +2614,7 @@ - (void)sendElement:(NSXMLElement *)element dispatch_block_t block = ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) + if (self->state == STATE_XMPP_CONNECTED) { [self sendElement:element withTag:TAG_XMPP_WRITE_STREAM]; } @@ -2590,10 +2651,10 @@ - (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt ** dispatch_block_t block = ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) + if (self->state == STATE_XMPP_CONNECTED) { receipt = [[XMPPElementReceipt alloc] init]; - [receipts addObject:receipt]; + [self->receipts addObject:receipt]; [self sendElement:element withTag:TAG_XMPP_WRITE_RECEIPT]; } @@ -2688,9 +2749,9 @@ - (void)resendMyPresence { dispatch_block_t block = ^{ @autoreleasepool { - if (myPresence && [[myPresence type] isEqualToString:@"available"]) + if (self->myPresence && [[self->myPresence type] isEqualToString:@"available"]) { - [self sendElement:myPresence]; + [self sendElement:self->myPresence]; } }}; @@ -2711,17 +2772,17 @@ - (void)sendAuthElement:(NSXMLElement *)element { dispatch_block_t block = ^{ @autoreleasepool { - if (state == STATE_XMPP_AUTH) + if (self->state == STATE_XMPP_AUTH) { NSString *outgoingStr = [element compactXMLString]; NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; XMPPLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; + self->numberOfBytesSent += [outgoingData length]; - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_XMPP_WRITE - tag:TAG_XMPP_WRITE_STREAM]; + [self->asyncSocket writeData:outgoingData + withTimeout:TIMEOUT_XMPP_WRITE + tag:TAG_XMPP_WRITE_STREAM]; } else { @@ -2746,17 +2807,17 @@ - (void)sendBindElement:(NSXMLElement *)element { dispatch_block_t block = ^{ @autoreleasepool { - if (state == STATE_XMPP_BINDING) + if (self->state == STATE_XMPP_BINDING) { NSString *outgoingStr = [element compactXMLString]; NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding]; XMPPLogSend(@"SEND: %@", outgoingStr); - numberOfBytesSent += [outgoingData length]; + self->numberOfBytesSent += [outgoingData length]; - [asyncSocket writeData:outgoingData - withTimeout:TIMEOUT_XMPP_WRITE - tag:TAG_XMPP_WRITE_STREAM]; + [self->asyncSocket writeData:outgoingData + withTimeout:TIMEOUT_XMPP_WRITE + tag:TAG_XMPP_WRITE_STREAM]; } else { @@ -2790,8 +2851,8 @@ - (void)receiveIQ:(XMPPIQ *)iq // But still go through the stanzaQueue in order to guarantee in-order-delivery of all received stanzas. dispatch_async(willReceiveStanzaQueue, ^{ - dispatch_async(xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { + if (self->state == STATE_XMPP_CONNECTED) { [self continueReceiveIQ:iq]; } }}); @@ -2830,14 +2891,14 @@ - (void)receiveIQ:(XMPPIQ *)iq }}); } - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) + if (self->state == STATE_XMPP_CONNECTED) { if (modifiedIQ) [self continueReceiveIQ:modifiedIQ]; else - [multicastDelegate xmppStreamDidFilterStanza:self]; + [self->multicastDelegate xmppStreamDidFilterStanza:self]; } }}); }}); @@ -2864,9 +2925,9 @@ - (void)receiveMessage:(XMPPMessage *)message // But still go through the stanzaQueue in order to guarantee in-order-delivery of all received stanzas. dispatch_async(willReceiveStanzaQueue, ^{ - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) { + if (self->state == STATE_XMPP_CONNECTED) { [self continueReceiveMessage:message]; } }}); @@ -2905,14 +2966,14 @@ - (void)receiveMessage:(XMPPMessage *)message }}); } - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) + if (self->state == STATE_XMPP_CONNECTED) { if (modifiedMessage) [self continueReceiveMessage:modifiedMessage]; else - [multicastDelegate xmppStreamDidFilterStanza:self]; + [self->multicastDelegate xmppStreamDidFilterStanza:self]; } }}); }}); @@ -2939,9 +3000,9 @@ - (void)receivePresence:(XMPPPresence *)presence // But still go through the stanzaQueue in order to guarantee in-order-delivery of all received stanzas. dispatch_async(willReceiveStanzaQueue, ^{ - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) { + if (self->state == STATE_XMPP_CONNECTED) { [self continueReceivePresence:presence]; } }}); @@ -2980,14 +3041,14 @@ - (void)receivePresence:(XMPPPresence *)presence }}); } - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { - if (state == STATE_XMPP_CONNECTED) + if (self->state == STATE_XMPP_CONNECTED) { if (modifiedPresence) [self continueReceivePresence:presence]; else - [multicastDelegate xmppStreamDidFilterStanza:self]; + [self->multicastDelegate xmppStreamDidFilterStanza:self]; } }}); }}); @@ -3114,7 +3175,7 @@ - (void)injectElement:(NSXMLElement *)element dispatch_block_t block = ^{ @autoreleasepool { - if (state != STATE_XMPP_CONNECTED) + if (self->state != STATE_XMPP_CONNECTED) { return_from_block; } @@ -3147,13 +3208,13 @@ - (void)injectElement:(NSXMLElement *)element { [self receivePresence:[XMPPPresence presenceFromElement:element]]; } - else if ([customElementNames countForObject:elementName]) + else if ([self->customElementNames countForObject:elementName]) { - [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + [self->multicastDelegate xmppStream:self didReceiveCustomElement:element]; } else { - [multicastDelegate xmppStream:self didReceiveError:element]; + [self->multicastDelegate xmppStream:self didReceiveError:element]; } } }}; @@ -3168,12 +3229,12 @@ - (void)registerCustomElementNames:(NSSet *)names { dispatch_block_t block = ^{ - if (customElementNames == nil) - customElementNames = [[NSCountedSet alloc] init]; + if (self->customElementNames == nil) + self->customElementNames = [[NSCountedSet alloc] init]; for (NSString *name in names) { - [customElementNames addObject:name]; + [self->customElementNames addObject:name]; } }; @@ -3189,7 +3250,7 @@ - (void)unregisterCustomElementNames:(NSSet *)names for (NSString *name in names) { - [customElementNames removeObject:name]; + [self->customElementNames removeObject:name]; } }; @@ -3371,7 +3432,7 @@ - (void)startTLS }}); } - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { [self continueStartTLS:settings]; @@ -3402,7 +3463,7 @@ - (void)continueStartTLS:(NSMutableDictionary *)settings if ([expectedCertName length] > 0) { - [settings setObject:expectedCertName forKey:(NSString *)kCFStreamSSLPeerName]; + settings[(NSString *) kCFStreamSSLPeerName] = expectedCertName; } } @@ -3471,7 +3532,7 @@ - (void)handleStreamFeatures // We must abort the connection as the server doesn't support our requirements. NSString *errMsg = @"The server does not support startTLS. And the startTLSPolicy is Required."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; otherError = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info]; @@ -3567,7 +3628,7 @@ - (void)handleAuth:(NSXMLElement *)authResponse XMPPHandleAuthResponse result = [auth handleAuth:authResponse]; - if (result == XMPP_AUTH_SUCCESS) + if (result == XMPPHandleAuthResponseSuccess) { // We are successfully authenticated (via sasl:digest-md5) [self setIsAuthenticated:YES]; @@ -3605,7 +3666,7 @@ - (void)handleAuth:(NSXMLElement *)authResponse auth = nil; } - else if (result == XMPP_AUTH_FAIL) + else if (result == XMPPHandleAuthResponseFailed) { // Revert back to connected state (from authenticating state) state = STATE_XMPP_CONNECTED; @@ -3617,7 +3678,7 @@ - (void)handleAuth:(NSXMLElement *)authResponse auth = nil; } - else if (result == XMPP_AUTH_CONTINUE) + else if (result == XMPPHandleAuthResponseContinue) { // Authentication continues. // State doesn't change. @@ -3668,7 +3729,7 @@ - (void)startBinding } } - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { if (delegateCustomBinding) [self startCustomBinding:delegateCustomBinding]; @@ -3688,14 +3749,14 @@ - (void)startCustomBinding:(id )delegateCustomBinding NSError *bindError = nil; XMPPBindResult result = [customBinding start:&bindError]; - if (result == XMPP_BIND_CONTINUE) + if (result == XMPPBindResultContinue) { // Expected result // Wait for reply from server, and forward to customBinding module. } else { - if (result == XMPP_BIND_SUCCESS) + if (result == XMPPBindResultSuccess) { // It appears binding isn't needed (perhaps handled via auth) @@ -3706,14 +3767,14 @@ - (void)startCustomBinding:(id )delegateCustomBinding [self continuePostBinding:skipStartSessionOverride]; } - else if (result == XMPP_BIND_FAIL_FALLBACK) + else if (result == XMPPBindResultFailFallback) { // Custom binding isn't available for whatever reason, // but the module has requested we fallback to standard binding. [self startStandardBinding]; } - else if (result == XMPP_BIND_FAIL_ABORT) + else if (result == XMPPBindResultFailAbort) { // Custom binding failed, // and the module requested we abort. @@ -3733,13 +3794,13 @@ - (void)handleCustomBinding:(NSXMLElement *)response NSError *bindError = nil; XMPPBindResult result = [customBinding handleBind:response withError:&bindError]; - if (result == XMPP_BIND_CONTINUE) + if (result == XMPPBindResultContinue) { // Binding still in progress } else { - if (result == XMPP_BIND_SUCCESS) + if (result == XMPPBindResultSuccess) { // Binding complete. Continue. @@ -3750,14 +3811,14 @@ - (void)handleCustomBinding:(NSXMLElement *)response [self continuePostBinding:skipStartSessionOverride]; } - else if (result == XMPP_BIND_FAIL_FALLBACK) + else if (result == XMPPBindResultFailFallback) { // Custom binding failed for whatever reason, // but the module has requested we fallback to standard binding. [self startStandardBinding]; } - else if (result == XMPP_BIND_FAIL_ABORT) + else if (result == XMPPBindResultFailAbort) { // Custom binding failed, // and the module requested we abort. @@ -3906,7 +3967,7 @@ - (void)handleStandardBinding:(NSXMLElement *)response } } - dispatch_async(xmppQueue, ^{ @autoreleasepool { + dispatch_async(self->xmppQueue, ^{ @autoreleasepool { [self continueHandleStandardBinding:alternativeResource]; @@ -3997,7 +4058,10 @@ - (void)continuePostBinding:(BOOL)skipStartSessionOverride // Check to see if a session is required // Don't forget about that NSXMLElement bug you reported to apple (xmlns is required or element won't be found) NSXMLElement *f_session = [features elementForName:@"session" xmlns:@"urn:ietf:params:xml:ns:xmpp-session"]; - + if (f_session && [f_session elementForName:@"optional"]) { + skipStartSessionOverride = YES; + } + if (f_session && !skipStartSession && !skipStartSessionOverride) { NSXMLElement *session = [NSXMLElement elementWithName:@"session"]; @@ -4070,7 +4134,7 @@ - (void)tryNextSrvResult while (srvResultsIndex < [srvResults count]) { - XMPPSRVRecord *srvRecord = [srvResults objectAtIndex:srvResultsIndex]; + XMPPSRVRecord *srvRecord = srvResults[srvResultsIndex]; NSString *srvHost = srvRecord.target; UInt16 srvPort = srvRecord.port; @@ -4157,12 +4221,15 @@ - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UI #if TARGET_OS_IPHONE { - if (self.enableBackgroundingOnSocket) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (self.enableBackgroundingOnSocket) +#pragma clang diagnostic pop { __block BOOL result; [asyncSocket performBlock:^{ - result = [asyncSocket enableBackgroundingOnSocket]; + result = [self->asyncSocket enableBackgroundingOnSocket]; }]; if (result) @@ -4291,7 +4358,7 @@ - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag return; } - XMPPElementReceipt *receipt = [receipts objectAtIndex:0]; + XMPPElementReceipt *receipt = receipts[0]; [receipt signalSuccess]; [receipts removeObjectAtIndex:0]; } @@ -4503,7 +4570,7 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element { // We've just read in the stream features // We consider this part of the root element, so we'll add it (replacing any previously sent features) - [rootElement setChildren:[NSArray arrayWithObject:element]]; + [rootElement setChildren:@[element]]; // Call a method to handle any requirements set forth in the features [self handleStreamFeatures]; @@ -4774,12 +4841,12 @@ - (void)registerModule:(XMPPModule *)module // Register module - [registeredModules addObject:module]; + [self->registeredModules addObject:module]; // Add auto delegates (if there are any) NSString *className = NSStringFromClass([module class]); - GCDMulticastDelegate *autoDelegates = [autoDelegateDict objectForKey:className]; + GCDMulticastDelegate *autoDelegates = self->autoDelegateDict[className]; GCDMulticastDelegateEnumerator *autoDelegatesEnumerator = [autoDelegates delegateEnumerator]; id delegate; @@ -4792,7 +4859,7 @@ - (void)registerModule:(XMPPModule *)module // Notify our own delegate(s) - [multicastDelegate xmppStream:self didRegisterModule:module]; + [self->multicastDelegate xmppStream:self didRegisterModule:module]; }}; @@ -4814,12 +4881,12 @@ - (void)unregisterModule:(XMPPModule *)module // Notify our own delegate(s) - [multicastDelegate xmppStream:self willUnregisterModule:module]; + [self->multicastDelegate xmppStream:self willUnregisterModule:module]; // Remove auto delegates (if there are any) NSString *className = NSStringFromClass([module class]); - GCDMulticastDelegate *autoDelegates = [autoDelegateDict objectForKey:className]; + GCDMulticastDelegate *autoDelegates = self->autoDelegateDict[className]; GCDMulticastDelegateEnumerator *autoDelegatesEnumerator = [autoDelegates delegateEnumerator]; id delegate; @@ -4837,7 +4904,7 @@ - (void)unregisterModule:(XMPPModule *)module // Unregister modules - [registeredModules removeObject:module]; + [self->registeredModules removeObject:module]; }}; @@ -4862,7 +4929,7 @@ - (void)autoAddDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQue // Add the delegate to all currently registered modules of the given class. - for (XMPPModule *module in registeredModules) + for (XMPPModule *module in self->registeredModules) { if ([module isKindOfClass:aClass]) { @@ -4873,12 +4940,12 @@ - (void)autoAddDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQue // Add the delegate to list of auto delegates for the given class. // It will be added as a delegate to future registered modules of the given class. - id delegates = [autoDelegateDict objectForKey:className]; + id delegates = self->autoDelegateDict[className]; if (delegates == nil) { delegates = [[GCDMulticastDelegate alloc] init]; - [autoDelegateDict setObject:delegates forKey:className]; + self->autoDelegateDict[className] = delegates; } [delegates addDelegate:delegate delegateQueue:delegateQueue]; @@ -4907,7 +4974,7 @@ - (void)removeAutoDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegate { // Remove the delegate from all currently registered modules of ANY class. - for (XMPPModule *module in registeredModules) + for (XMPPModule *module in self->registeredModules) { [module removeDelegate:delegate delegateQueue:delegateQueue]; } @@ -4915,7 +4982,7 @@ - (void)removeAutoDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegate // Remove the delegate from list of auto delegates for all classes, // so that it will not be auto added as a delegate to future registered modules. - for (GCDMulticastDelegate *delegates in [autoDelegateDict objectEnumerator]) + for (GCDMulticastDelegate *delegates in [self->autoDelegateDict objectEnumerator]) { [delegates removeDelegate:delegate delegateQueue:delegateQueue]; } @@ -4926,7 +4993,7 @@ - (void)removeAutoDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegate // Remove the delegate from all currently registered modules of the given class. - for (XMPPModule *module in registeredModules) + for (XMPPModule *module in self->registeredModules) { if ([module isKindOfClass:aClass]) { @@ -4937,12 +5004,12 @@ - (void)removeAutoDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegate // Remove the delegate from list of auto delegates for the given class, // so that it will not be added as a delegate to future registered modules of the given class. - GCDMulticastDelegate *delegates = [autoDelegateDict objectForKey:className]; + GCDMulticastDelegate *delegates = self->autoDelegateDict[className]; [delegates removeDelegate:delegate delegateQueue:delegateQueue]; if ([delegates count] == 0) { - [autoDelegateDict removeObjectForKey:className]; + [self->autoDelegateDict removeObjectForKey:className]; } } @@ -4965,7 +5032,7 @@ - (void)enumerateModulesWithBlock:(void (^)(XMPPModule *module, NSUInteger idx, NSUInteger i = 0; BOOL stop = NO; - for (XMPPModule *module in registeredModules) + for (XMPPModule *module in self->registeredModules) { enumBlock(module, i, &stop); @@ -5001,16 +5068,7 @@ - (void)enumerateModulesOfClass:(Class)aClass withBlock:(void (^)(XMPPModule *mo + (NSString *)generateUUID { - NSString *result = nil; - - CFUUIDRef uuid = CFUUIDCreate(NULL); - if (uuid) - { - result = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid); - CFRelease(uuid); - } - - return result; + return [NSUUID UUID].UUIDString; } - (NSString *)generateUUID @@ -5018,6 +5076,13 @@ - (NSString *)generateUUID return [[self class] generateUUID]; } +/** Allocates and configures a new socket */ +- (GCDAsyncSocket*) newSocket { + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:xmppQueue]; + socket.IPv4PreferredOverIPv6 = !self.preferIPv6; + return socket; +} + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Extensions/BandwidthMonitor/XMPPBandwidthMonitor.m b/Extensions/BandwidthMonitor/XMPPBandwidthMonitor.m index 05a8e3bab5..0d00d5769f 100644 --- a/Extensions/BandwidthMonitor/XMPPBandwidthMonitor.m +++ b/Extensions/BandwidthMonitor/XMPPBandwidthMonitor.m @@ -30,7 +30,7 @@ - (double)outgoingBandwidth __block double result = 0.0; dispatch_block_t block = ^{ - result = smoothedAverageOutgoingBandwidth; + result = self->smoothedAverageOutgoingBandwidth; }; if (dispatch_get_specific(moduleQueueTag)) @@ -46,7 +46,7 @@ - (double)incomingBandwidth __block double result = 0.0; dispatch_block_t block = ^{ - result = smoothedAverageIncomingBandwidth; + result = self->smoothedAverageIncomingBandwidth; }; if (dispatch_get_specific(moduleQueueTag)) diff --git a/Extensions/CoreDataStorage/XMPPCoreDataStorage.m b/Extensions/CoreDataStorage/XMPPCoreDataStorage.m index 567ac44bff..10ffda8774 100644 --- a/Extensions/CoreDataStorage/XMPPCoreDataStorage.m +++ b/Extensions/CoreDataStorage/XMPPCoreDataStorage.m @@ -88,7 +88,11 @@ - (NSString *)managedObjectModelName - (NSBundle *)managedObjectModelBundle { +#if SWIFT_PACKAGE && defined(SWIFTPM_MODULE_BUNDLE) + return SWIFTPM_MODULE_BUNDLE; +#else return [NSBundle bundleForClass:[self class]]; +#endif } - (NSString *)defaultDatabaseFileName @@ -332,7 +336,7 @@ - (NSUInteger)saveThreshold __block NSUInteger result; dispatch_sync(storageQueue, ^{ - result = saveThreshold; + result = self->saveThreshold; }); return result; @@ -342,7 +346,7 @@ - (NSUInteger)saveThreshold - (void)setSaveThreshold:(NSUInteger)newSaveThreshold { dispatch_block_t block = ^{ - saveThreshold = newSaveThreshold; + self->saveThreshold = newSaveThreshold; }; if (dispatch_get_specific(storageQueueTag)) @@ -375,13 +379,13 @@ - (XMPPJID *)myJIDForXMPPStream:(XMPPStream *)stream NSNumber *key = [NSNumber xmpp_numberWithPtr:(__bridge void *)stream]; - result = (XMPPJID *)[myJidCache objectForKey:key]; + result = (XMPPJID *) self->myJidCache[key]; if (!result) { result = [stream myJID]; if (result) { - [myJidCache setObject:result forKey:key]; + self->myJidCache[key] = result; } } }}; @@ -411,7 +415,7 @@ - (void)updateJidCache:(NSNotification *)notification dispatch_block_t block = ^{ @autoreleasepool { NSNumber *key = [NSNumber xmpp_numberWithPtr:(__bridge void *)stream]; - XMPPJID *cachedJID = [myJidCache objectForKey:key]; + XMPPJID *cachedJID = self->myJidCache[key]; if (cachedJID) { @@ -421,13 +425,13 @@ - (void)updateJidCache:(NSNotification *)notification { if (![cachedJID isEqualToJID:newJID]) { - [myJidCache setObject:newJID forKey:key]; + self->myJidCache[key] = newJID; [self didChangeCachedMyJID:newJID forXMPPStream:stream]; } } else { - [myJidCache removeObjectForKey:key]; + [self->myJidCache removeObjectForKey:key]; [self didChangeCachedMyJID:nil forXMPPStream:stream]; } } @@ -445,30 +449,27 @@ - (void)updateJidCache:(NSNotification *)notification - (NSString *)persistentStoreDirectory { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory(); - - // Attempt to find a name for this application - NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - if (appName == nil) { - appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; - } - - if (appName == nil) { - appName = @"xmppframework"; - } - - - NSString *result = [basePath stringByAppendingPathComponent:appName]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - - if (![fileManager fileExistsAtPath:result]) - { - [fileManager createDirectoryAtPath:result withIntermediateDirectories:YES attributes:nil error:nil]; - } - - return result; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + NSString *basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory(); + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Previously the Peristent Story Directory was based on the Bundle Display Name but this can be Localized + // If Peristent Story Directory already exists we will use that + NSString *bundleDisplayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (bundleDisplayName) { + NSString *legacyPersistentStoreDirectory = [basePath stringByAppendingPathComponent:bundleDisplayName]; + if ([fileManager fileExistsAtPath:legacyPersistentStoreDirectory]) { + return legacyPersistentStoreDirectory; + } + } + + // Peristent Story Directory now uses the Bundle Identifier + NSString *bundleIdentifier = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; + NSString *persistentStoreDirectory = [basePath stringByAppendingPathComponent:bundleIdentifier]; + if (![fileManager fileExistsAtPath:persistentStoreDirectory]) { + [fileManager createDirectoryAtPath:persistentStoreDirectory withIntermediateDirectories:YES attributes:nil error:nil]; + } + return persistentStoreDirectory; } - (NSManagedObjectModel *)managedObjectModel @@ -480,9 +481,9 @@ - (NSManagedObjectModel *)managedObjectModel dispatch_block_t block = ^{ @autoreleasepool { - if (managedObjectModel) + if (self->managedObjectModel) { - result = managedObjectModel; + result = self->managedObjectModel; return; } @@ -503,38 +504,38 @@ - (NSManagedObjectModel *)managedObjectModel NSURL *momUrl = [NSURL fileURLWithPath:momPath]; - managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:momUrl] copy]; + self->managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:momUrl] copy]; } else { XMPPLogWarn(@"%@: Couldn't find managedObjectModel file - %@", [self class], momName); } - if([NSAttributeDescription instancesRespondToSelector:@selector(setAllowsExternalBinaryDataStorage:)]) - { - if(autoAllowExternalBinaryDataStorage) - { - NSArray *entities = [managedObjectModel entities]; - - for(NSEntityDescription *entity in entities) - { - NSDictionary *attributesByName = [entity attributesByName]; - - [attributesByName enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - - if([obj attributeType] == NSBinaryDataAttributeType) - { - [obj setAllowsExternalBinaryDataStorage:YES]; - } - - }]; - } - - } + if([NSAttributeDescription instancesRespondToSelector:@selector(setAllowsExternalBinaryDataStorage:)]) + { + if(self->autoAllowExternalBinaryDataStorage) + { + NSArray *entities = [self->managedObjectModel entities]; + + for(NSEntityDescription *entity in entities) + { + NSDictionary *attributesByName = [entity attributesByName]; + + [attributesByName enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + + if([obj attributeType] == NSBinaryDataAttributeType) + { + [obj setAllowsExternalBinaryDataStorage:YES]; + } + + }]; + } + + } } - result = managedObjectModel; + result = self->managedObjectModel; }}; if (dispatch_get_specific(storageQueueTag)) @@ -554,9 +555,9 @@ - (NSPersistentStoreCoordinator *)persistentStoreCoordinator dispatch_block_t block = ^{ @autoreleasepool { - if (persistentStoreCoordinator) + if (self->persistentStoreCoordinator) { - result = persistentStoreCoordinator; + result = self->persistentStoreCoordinator; return; } @@ -568,42 +569,42 @@ - (NSPersistentStoreCoordinator *)persistentStoreCoordinator XMPPLogVerbose(@"%@: Creating persistentStoreCoordinator", [self class]); - persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; + self->persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; - if (databaseFileName) + if (self->databaseFileName) { // SQLite persistent store NSString *docsPath = [self persistentStoreDirectory]; - NSString *storePath = [docsPath stringByAppendingPathComponent:databaseFileName]; + NSString *storePath = [docsPath stringByAppendingPathComponent:self->databaseFileName]; if (storePath) { // If storePath is nil, then NSURL will throw an exception - - if(autoRemovePreviousDatabaseFile) - { - if ([[NSFileManager defaultManager] fileExistsAtPath:storePath]) - { - [[NSFileManager defaultManager] removeItemAtPath:storePath error:nil]; - } - } + + if(self->autoRemovePreviousDatabaseFile) + { + if ([[NSFileManager defaultManager] fileExistsAtPath:storePath]) + { + [[NSFileManager defaultManager] removeItemAtPath:storePath error:nil]; + } + } - [self willCreatePersistentStoreWithPath:storePath options:storeOptions]; + [self willCreatePersistentStoreWithPath:storePath options:self->storeOptions]; NSError *error = nil; - BOOL didAddPersistentStore = [self addPersistentStoreWithPath:storePath options:storeOptions error:&error]; + BOOL didAddPersistentStore = [self addPersistentStoreWithPath:storePath options:self->storeOptions error:&error]; - if(autoRecreateDatabaseFile && !didAddPersistentStore) + if(self->autoRecreateDatabaseFile && !didAddPersistentStore) { [[NSFileManager defaultManager] removeItemAtPath:storePath error:NULL]; - didAddPersistentStore = [self addPersistentStoreWithPath:storePath options:storeOptions error:&error]; + didAddPersistentStore = [self addPersistentStoreWithPath:storePath options:self->storeOptions error:&error]; } if (!didAddPersistentStore) { - [self didNotAddPersistentStoreWithPath:storePath options:storeOptions error:error]; + [self didNotAddPersistentStoreWithPath:storePath options:self->storeOptions error:error]; } } else @@ -616,16 +617,16 @@ - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { // In-Memory persistent store - [self willCreatePersistentStoreWithPath:nil options:storeOptions]; + [self willCreatePersistentStoreWithPath:nil options:self->storeOptions]; NSError *error = nil; - if (![self addPersistentStoreWithPath:nil options:storeOptions error:&error]) + if (![self addPersistentStoreWithPath:nil options:self->storeOptions error:&error]) { - [self didNotAddPersistentStoreWithPath:nil options:storeOptions error:error]; + [self didNotAddPersistentStoreWithPath:nil options:self->storeOptions error:error]; } } - result = persistentStoreCoordinator; + result = self->persistentStoreCoordinator; }}; @@ -749,11 +750,11 @@ - (void)managedObjectContextDidSave:(NSNotification *)notification dispatch_async(dispatch_get_main_queue(), ^{ // http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different - for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) { - [[mainThreadManagedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil]; - } + for (NSManagedObject *object in [notification userInfo][NSUpdatedObjectsKey]) { + [[self->mainThreadManagedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil]; + } - [mainThreadManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; + [self->mainThreadManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; [self mainThreadManagedObjectContextDidMergeChanges]; }); } @@ -764,7 +765,7 @@ - (BOOL)autoRemovePreviousDatabaseFile __block BOOL result = NO; dispatch_block_t block = ^{ @autoreleasepool { - result = autoRemovePreviousDatabaseFile; + result = self->autoRemovePreviousDatabaseFile; }}; if (dispatch_get_specific(storageQueueTag)) @@ -778,7 +779,7 @@ - (BOOL)autoRemovePreviousDatabaseFile - (void)setAutoRemovePreviousDatabaseFile:(BOOL)flag { dispatch_block_t block = ^{ - autoRemovePreviousDatabaseFile = flag; + self->autoRemovePreviousDatabaseFile = flag; }; if (dispatch_get_specific(storageQueueTag)) @@ -792,7 +793,7 @@ - (BOOL)autoRecreateDatabaseFile __block BOOL result = NO; dispatch_block_t block = ^{ @autoreleasepool { - result = autoRecreateDatabaseFile; + result = self->autoRecreateDatabaseFile; }}; if (dispatch_get_specific(storageQueueTag)) @@ -806,7 +807,7 @@ - (BOOL)autoRecreateDatabaseFile - (void)setAutoRecreateDatabaseFile:(BOOL)flag { dispatch_block_t block = ^{ - autoRecreateDatabaseFile = flag; + self->autoRecreateDatabaseFile = flag; }; if (dispatch_get_specific(storageQueueTag)) @@ -820,7 +821,7 @@ - (BOOL)autoAllowExternalBinaryDataStorage __block BOOL result = NO; dispatch_block_t block = ^{ @autoreleasepool { - result = autoAllowExternalBinaryDataStorage; + result = self->autoAllowExternalBinaryDataStorage; }}; if (dispatch_get_specific(storageQueueTag)) @@ -834,7 +835,7 @@ - (BOOL)autoAllowExternalBinaryDataStorage - (void)setAutoAllowExternalBinaryDataStorage:(BOOL)flag { dispatch_block_t block = ^{ - autoAllowExternalBinaryDataStorage = flag; + self->autoAllowExternalBinaryDataStorage = flag; }; if (dispatch_get_specific(storageQueueTag)) @@ -948,9 +949,9 @@ - (void)executeBlock:(dispatch_block_t)block // Since this is a synchronous request, we want to return as quickly as possible. // So we delay the maybeSave operation til later. - dispatch_async(storageQueue, ^{ @autoreleasepool { + dispatch_async(self->storageQueue, ^{ @autoreleasepool { - [self maybeSave:OSAtomicDecrement32(&pendingRequests)]; + [self maybeSave:OSAtomicDecrement32(&self->pendingRequests)]; }}); }}); @@ -974,14 +975,14 @@ - (void)scheduleBlock:(dispatch_block_t)block dispatch_async(storageQueue, ^{ @autoreleasepool { block(); - [self maybeSave:OSAtomicDecrement32(&pendingRequests)]; + [self maybeSave:OSAtomicDecrement32(&self->pendingRequests)]; }}); } - (void)addWillSaveManagedObjectContextBlock:(void (^)(void))willSaveBlock { dispatch_block_t block = ^{ - [willSaveManagedObjectContextBlocks addObject:[willSaveBlock copy]]; + [self->willSaveManagedObjectContextBlocks addObject:[willSaveBlock copy]]; }; if (dispatch_get_specific(storageQueueTag)) @@ -993,7 +994,7 @@ - (void)addWillSaveManagedObjectContextBlock:(void (^)(void))willSaveBlock - (void)addDidSaveManagedObjectContextBlock:(void (^)(void))didSaveBlock { dispatch_block_t block = ^{ - [didSaveManagedObjectContextBlocks addObject:[didSaveBlock copy]]; + [self->didSaveManagedObjectContextBlocks addObject:[didSaveBlock copy]]; }; if (dispatch_get_specific(storageQueueTag)) diff --git a/Extensions/FileTransfer/XMPPFileTransfer.h b/Extensions/FileTransfer/XMPPFileTransfer.h new file mode 100644 index 0000000000..7325005a23 --- /dev/null +++ b/Extensions/FileTransfer/XMPPFileTransfer.h @@ -0,0 +1,81 @@ +// +// Created by Jonathon Staff on 10/21/14. +// Copyright (c) 2014 nplexity, LLC. All rights reserved. +// + +#import +#import "XMPP.h" +#import "XMPPModule.h" +#import "TURNSocket.h" + +@import CocoaAsyncSocket; + +@class XMPPIDTracker; + +typedef NS_OPTIONS(uint8_t, XMPPFileTransferStreamMethod) { + XMPPFileTransferStreamMethodBytestreams = 1 << 0, // If set, SOCKS5 connections allowed + XMPPFileTransferStreamMethodIBB = 1 << 1, // If set, IBB connections allowed +// XMPPFileTransferStreamMethodJingle = 1 << 2 // If set, Jingle connections allowed + // Note that Jingle is not yet implemented +}; + +/** +* This class defines common elements of the file transfer process that apply to +* both outgoing and incoming transfers. +* +* You'll find more detailed documentation in each of the implementation files. +* +* By default, the stream-method priority is as follows: +* +* 1. SOCKS5 Direct Connection (http://xmpp.org/extensions/xep-0065.html#direct) +* 2. SOCKS5 Mediated (http://xmpp.org/extensions/xep-0065.html#mediated) +* 3. IBB (http://xmpp.org/extensions/xep-0047.html) +*/ +@interface XMPPFileTransfer : XMPPModule { + XMPPFileTransferStreamMethod _streamMethods; + XMPPIDTracker *_idTracker; + GCDAsyncSocket *_asyncSocket; + NSMutableArray *_streamhosts; +} + +/** +* The streamID ("sid") for the file transfer. +*/ +@property (nonatomic, copy) NSString *sid; + +/** +* Use this to disable file transfers via direct connection. +* +* If set to YES, SOCKS5 transfers will only take place if there is a proxy that +* works. If set to NO, SOCKS5 transfers will attempt a direct connection first +* and fall back to a proxy if the direct connection doesn't work. +* +* The default value is NO. +*/ +@property (nonatomic, assign) BOOL disableDirectTransfers; + +/** +* Use this to disable file transfers via SOCKS5. +* +* If set to YES, SOCKS5 transfers will not be used. This means that the +* recipient must support IBB transfers or the transfer will fail. If set to NO, +* a SOCKS5 connection will be attempted first, since this should be the +* preferred method of transfer. +* +* The default value is NO. +*/ +@property (nonatomic, assign) BOOL disableSOCKS5; + +/** +* Use this to disable file transfers via IBB. +* +* If set to YES, IBB transfers will not be used. This means that the +* recipient must support SOCKS5 transfers or the transfer will fail. If set to +* NO, a SOCKS5 connection will be attempted first, since this should be the +* preferred method of transfer. +* +* The default value is NO. +*/ +@property (nonatomic, assign) BOOL disableIBB; + +@end diff --git a/Extensions/FileTransfer/XMPPFileTransfer.m b/Extensions/FileTransfer/XMPPFileTransfer.m new file mode 100644 index 0000000000..1334c8bfd5 --- /dev/null +++ b/Extensions/FileTransfer/XMPPFileTransfer.m @@ -0,0 +1,11 @@ +// +// Created by Jonathon Staff on 10/21/14. +// Copyright (c) 2014 nplexity, LLC. All rights reserved. +// + +#import "XMPPFileTransfer.h" + + +@implementation XMPPFileTransfer + +@end \ No newline at end of file diff --git a/Extensions/FileTransfer/XMPPIncomingFileTransfer.h b/Extensions/FileTransfer/XMPPIncomingFileTransfer.h new file mode 100644 index 0000000000..2da822ccb0 --- /dev/null +++ b/Extensions/FileTransfer/XMPPIncomingFileTransfer.h @@ -0,0 +1,80 @@ +// +// Created by Jonathon Staff on 10/21/14. +// Copyright (c) 2014 nplexity, LLC. All rights reserved. +// + +#import +#import "XMPPFileTransfer.h" + +@class XMPPIQ; + +@interface XMPPIncomingFileTransfer : XMPPFileTransfer + +/** +* (Optional) +* +* Specifies whether or not file transfers should automatically be accepted. If +* set to YES, you will be notified of an incoming Stream Initiation Offer, but +* it will be accepted for you. +* +* The default value is NO. +*/ +@property (nonatomic, assign) BOOL autoAcceptFileTransfers; + +/** +* Sends a response to the file transfer initiator accepting the Stream +* Initiation offer. It will automatically determine the best transfer method +* (either SOCKS5 or IBB) based on what the sender offers as options. +* +* If you've set autoAcceptFileTransfers to YES, this method will be invoked for +* you automatically. +* +* @param offer IQ stanza representing the SI offer (this should be provided by +* the delegate to you). +*/ +- (void)acceptSIOffer:(XMPPIQ *)offer; + +@end + + +#pragma mark - XMPPIncomingFileTransferDelegate + +@protocol XMPPIncomingFileTransferDelegate +@optional + +/** +* Implement this method to receive notifications of a failed incoming file +* transfer. +* +* @param sender XMPPIncomingFileTransfer object invoking this delegate method. +* @param error NSError containing more details of the failure. +*/ +- (void)xmppIncomingFileTransfer:(XMPPIncomingFileTransfer *)sender + didFailWithError:(NSError *)error; + +/** +* Implement this method to receive notification of an incoming Stream +* Initiation offer. Keep in mind that if you haven't set +* autoAcceptFileTransfers to YES, then it will be your responsibility to call +* acceptSIOffer: using the sender and offer provided to you. +* +* @param sender XMPPIncomingFileTransfer object invoking this delegate method. +* @param offer IQ stanza containing a Stream Initiation offer. +*/ +- (void)xmppIncomingFileTransfer:(XMPPIncomingFileTransfer *)sender + didReceiveSIOffer:(XMPPIQ *)offer; + +/** +* Implement this method to receive notifications of a successful incoming file +* transfer. It will only be invoked if all of the data is received +* successfully. +* +* @param sender XMPPIncomingFileTransfer object invoking this delegate method. +* @param data NSData for you to handle (probably save this or display it). +* @param named Name of the file you just received. +*/ +- (void)xmppIncomingFileTransfer:(XMPPIncomingFileTransfer *)sender + didSucceedWithData:(NSData *)data + named:(NSString *)name; + +@end diff --git a/Extensions/FileTransfer/XMPPIncomingFileTransfer.m b/Extensions/FileTransfer/XMPPIncomingFileTransfer.m new file mode 100644 index 0000000000..361a1d0275 --- /dev/null +++ b/Extensions/FileTransfer/XMPPIncomingFileTransfer.m @@ -0,0 +1,959 @@ +// +// Created by Jonathon Staff on 10/21/14. +// Copyright (c) 2014 nplexity, LLC. All rights reserved. +// + +#if !__has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import "XMPPIncomingFileTransfer.h" +#import "XMPPConstants.h" +#import "XMPPLogging.h" +#import "NSNumber+XMPP.h" +#import "NSData+XMPP.h" + +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // XMPP_LOG_LEVEL_VERBOSE | XMPP_LOG_FLAG_TRACE; +#else + static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +/** +* Tags for _asyncSocket handling. +*/ +#define SOCKS_TAG_WRITE_METHOD 101 +#define SOCKS_TAG_READ_METHOD 102 +#define SOCKS_TAG_WRITE_CONNECT 103 +#define SOCKS_TAG_READ_REPLY 104 +#define SOCKS_TAG_READ_ADDRESS 105 +#define SOCKS_TAG_READ_DATA 106 + +#define TIMEOUT_WRITE -1 +#define TIMEOUT_READ 5.0 + +// XMPP Incoming File Transfer State +typedef NS_ENUM(int, XMPPIFTState) { + XMPPIFTStateNone, + XMPPIFTStateWaitingForSIOffer, + XMPPIFTStateWaitingForStreamhosts, + XMPPIFTStateConnectingToStreamhosts, + XMPPIFTStateConnected, + XMPPIFTStateWaitingForIBBOpen, + XMPPIFTStateWaitingForIBBData +}; + +NSString *const XMPPIncomingFileTransferErrorDomain = @"XMPPIncomingFileTransferErrorDomain"; + +@interface XMPPIncomingFileTransfer () { + XMPPIFTState _transferState; + + XMPPJID *_senderJID; + + NSString *_streamhostsQueryId; + NSString *_streamhostUsed; + + NSMutableData *_receivedData; + NSString *_receivedFileName; + NSUInteger _totalDataSize; + NSUInteger _receivedDataSize; + + dispatch_source_t _ibbTimer; +} + +@end + +@implementation XMPPIncomingFileTransfer + + +#pragma mark - Lifecycle + +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue +{ + self = [super initWithDispatchQueue:queue]; + if (self) { + _transferState = XMPPIFTStateNone; + } + return self; +} + +/** +* Standard deconstructor. +*/ +- (void)dealloc +{ + XMPPLogTrace(); + + if (_transferState != XMPPIFTStateNone) { + XMPPLogWarn(@"%@: Deallocating prior to completion or cancellation.", THIS_FILE); + } + + if (_ibbTimer) + dispatch_source_cancel(_ibbTimer); +#if !OS_OBJECT_USE_OBJC + dispatch_release(_ibbTimer); + #endif + _ibbTimer = NULL; + + if (_asyncSocket.delegate == self) { + [_asyncSocket setDelegate:nil delegateQueue:NULL]; + [_asyncSocket disconnect]; + } +} + + +#pragma mark - Public Methods + +/** +* Public facing method for accepting a SI offer. If autoAcceptFileTransfers is +* set to YES, this method will do nothing, since the internal method is invoked +* automatically. +* +* @see sendSIOfferAcceptance: +*/ +- (void)acceptSIOffer:(XMPPIQ *)offer +{ + XMPPLogTrace(); + + if (!_autoAcceptFileTransfers) { + [self sendSIOfferAcceptance:offer]; + } +} + + +#pragma mark - Private Methods + +/** +* This method will send the device's identity in response to a `disco#info` +* query. In our case, we will send something close the following: +* +* +* +* +* +* +* +* +* +* +* +* This tells the requester who they're dealing with and which transfer types +* we support. If there's a better way than hard-coding these values, I'm open +* to suggestions. +*/ +- (void)sendIdentity:(XMPPIQ *)request +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"result" + to:request.from + elementID:request.elementID]; + [iq addAttributeWithName:@"from" stringValue:self->xmppStream.myJID.full]; + + NSXMLElement + *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPDiscoInfoNamespace]; + + NSXMLElement *identity = [NSXMLElement elementWithName:@"identity"]; + [identity addAttributeWithName:@"category" stringValue:@"client"]; + [identity addAttributeWithName:@"type" stringValue:@"ios-osx"]; + [query addChild:identity]; + + NSXMLElement *feature = [NSXMLElement elementWithName:@"feature"]; + [feature addAttributeWithName:@"var" stringValue:XMPPSINamespace]; + [query addChild:feature]; + + NSXMLElement *feature1 = [NSXMLElement elementWithName:@"feature"]; + [feature1 addAttributeWithName:@"var" stringValue:XMPPSIProfileFileTransferNamespace]; + [query addChild:feature1]; + + if (!self.disableSOCKS5) { + NSXMLElement *feature2 = [NSXMLElement elementWithName:@"feature"]; + [feature2 addAttributeWithName:@"var" stringValue:XMPPBytestreamsNamespace]; + [query addChild:feature2]; + } + + if (!self.disableIBB) { + NSXMLElement *feature3 = [NSXMLElement elementWithName:@"feature"]; + [feature3 addAttributeWithName:@"var" stringValue:XMPPIBBNamespace]; + [query addChild:feature3]; + } + + [iq addChild:query]; + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method will send an IQ stanza accepting the SI offer. We need to choose +* which 'stream-method' we prefer to use. For now, we will be using IBB as the +* 'stream-method', but SOCKS5 is preferable. +* +* Take a look at XEP-0096 Examples 2 and 4 for more details. +*/ +- (void)sendSIOfferAcceptance:(XMPPIQ *)offer +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // Store the sender's JID + self->_senderJID = offer.from; + + // Store the sid for later use + NSXMLElement *inSi = offer.childElement; + self.sid = [inSi attributeStringValueForName:@"id"]; + + // Store the size of the incoming data for later use + NSXMLElement *inFile = [inSi elementForName:@"file"]; + self->_totalDataSize = [inFile attributeUnsignedIntegerValueForName:@"size"]; + + // Store the name of the file for later use + self->_receivedFileName = [inFile attributeStringValueForName:@"name"]; + + // Outgoing + XMPPIQ *iq = [XMPPIQ iqWithType:@"result" + to:offer.from + elementID:offer.elementID]; + + + NSXMLElement *si = [NSXMLElement elementWithName:@"si" xmlns:XMPPSINamespace]; + + NSXMLElement *feature = [NSXMLElement elementWithName:@"feature" + xmlns:XMPPFeatureNegNamespace]; + + NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; + [x addAttributeWithName:@"type" stringValue:@"submit"]; + + NSXMLElement *field = [NSXMLElement elementWithName:@"field"]; + [field addAttributeWithName:@"var" stringValue:@"stream-method"]; + + NSXMLElement *value = [NSXMLElement elementWithName:@"value"]; + + // Prefer SOCKS5 if it's not disabled. + if (!self.disableSOCKS5) { + [value setStringValue:XMPPBytestreamsNamespace]; + self->_transferState = XMPPIFTStateWaitingForStreamhosts; + } else { + [value setStringValue:XMPPIBBNamespace]; + self->_transferState = XMPPIFTStateWaitingForIBBOpen; + } + + [field addChild:value]; + [x addChild:field]; + [feature addChild:x]; + [si addChild:feature]; + [iq addChild:si]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + + +#pragma mark - IBB Methods + +/** +* This method will send an IQ stanza accepting the IBB request. See XEP-0047 +* Example 2 for more details. +*/ +- (void)sendIBBAcceptance:(XMPPIQ *)request +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"result" + to:request.from + elementID:request.elementID]; + [self->xmppStream sendElement:iq]; + + // Prepare to receive data + self->_receivedData = [NSMutableData new]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method is responsible for reading the incoming data from the IQ stanza +* and writing it to the member variable '_receivedData'. After successfully +* reading the data, a response (XEP-0047 Example 7) will be sent back to the +* sender. +*/ +- (void)processReceivedIBBDataIQ:(XMPPIQ *)received +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // Handle the scenario that the transfer is cancelled. + [self resetIBBTimer:20]; + + // Handle incoming data + NSXMLElement *dataElem = received.childElement; + NSData + *temp = [[NSData alloc] initWithBase64EncodedString:dataElem.stringValue options:0]; + [self->_receivedData appendData:temp]; + + // According the base64 encoding, it takes up 4/3 n bytes of space, so + // we need to find the size of the data before base64. + self->_receivedDataSize += (3 * dataElem.stringValue.length) / 4; + + XMPPLogVerbose(@"Downloaded %lu/%lu bytes in IBB transfer.", + (unsigned long) self->_receivedDataSize, (unsigned long) self->_totalDataSize); + + if (self->_receivedDataSize < self->_totalDataSize) { + // Send ack response + XMPPIQ *iq = [XMPPIQ iqWithType:@"result" + to:received.from + elementID:received.elementID]; + [self->xmppStream sendElement:iq]; + } else { + // We're finished! + XMPPLogInfo(@"Finished downloading IBB data."); + [self transferSuccess]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + + +#pragma mark - Util Methods + +/** +* This method determines whether or not the IQ stanza is a `disco#info` +* request. Should be in the following form: +* +* +* +* +*/ +- (BOOL)isDiscoInfoIQ:(XMPPIQ *)iq +{ + if (!iq) return NO; + NSXMLElement *query = iq.childElement; + return query != nil && [query.xmlns isEqualToString:XMPPDiscoInfoNamespace]; +} + +/** +* This method determines whether or not the the IQ stanza is a Stream +* Initiation Offer (XEP-0096 Examples 1 and 3). +*/ +- (BOOL)isSIOfferIQ:(XMPPIQ *)iq +{ + if (!iq) return NO; + if (![iq.type isEqualToString:@"set"]) return NO; + + NSXMLElement *si = iq.childElement; + if (!si || ![si.xmlns isEqualToString:XMPPSINamespace]) return NO; + + NSXMLElement *file = (NSXMLElement *) [si childAtIndex:0]; + if (!file || ![file.xmlns isEqualToString:XMPPSIProfileFileTransferNamespace]) return NO; + + NSXMLElement *feature = (NSXMLElement *) [si childAtIndex:1]; + return !(!feature || ![feature.xmlns isEqualToString:XMPPFeatureNegNamespace]); + + // Maybe there should be further verification, but I think this should be + // plenty... +} + +/** +* This method determines whether or not the IQ stanza is an IBB session request +* (XEP-0047 Example 1). +*/ +- (BOOL)isIBBOpenRequestIQ:(XMPPIQ *)iq +{ + if (!iq) return NO; + if (![iq.type isEqualToString:@"set"]) return NO; + + NSXMLElement *open = iq.childElement; + return !(!open || ![open.xmlns isEqualToString:XMPPIBBNamespace]); +} + +/** +* This method determines whether or not the IQ stanza is an IBB data stanza +* (XEP-0047 Example 6). +*/ +- (BOOL)isIBBDataIQ:(XMPPIQ *)iq +{ + if (!iq) return NO; + if (![iq.type isEqualToString:@"set"]) return NO; + + NSXMLElement *data = iq.childElement; + return !(!data || ![data.xmlns isEqualToString:XMPPIBBNamespace]); +} + +/** +* This method determines whether or not the IQ stanza contains a list of +* streamhosts as shown in XEP-0065 Example 12. +*/ +- (BOOL)isStreamhostsListIQ:(XMPPIQ *)iq +{ + if (!iq) return NO; + if (![iq.type isEqualToString:@"set"]) return NO; + + NSXMLElement *query = iq.childElement; + if (!query || ![[query attributeStringValueForName:@"sid"] isEqualToString:self.sid]) return NO; + + return [query elementsForName:@"streamhost"].count > 0; +} + +/** +* This method returns the SHA1 hash as per XEP-0065. +* +* The [address] MUST be SHA1(SID + Initiator JID + Target JID) and the output +* is hexadecimal encoded (not binary). +* +* Because this is an incoming file transfer, we are always the target. +*/ +- (NSData *)sha1Hash +{ + NSString *hashMe = + [NSString stringWithFormat:@"%@%@%@", self.sid, _senderJID.full, xmppStream.myJID.full]; + NSData *hashRaw = [[hashMe dataUsingEncoding:NSUTF8StringEncoding] xmpp_sha1Digest]; + NSData *hash = [[hashRaw xmpp_hexStringValue] dataUsingEncoding:NSUTF8StringEncoding]; + + XMPPLogVerbose(@"%@: hashMe : %@", THIS_FILE, hashMe); + XMPPLogVerbose(@"%@: hashRaw: %@", THIS_FILE, hashRaw); + XMPPLogVerbose(@"%@: hash : %@", THIS_FILE, hash); + + return hash; +} + +/** +* This method is called to clean up everything when the transfer fails. +*/ +- (void)failWithReason:(NSString *)causeOfFailure + error: + (NSError *)error +{ + XMPPLogTrace(); + XMPPLogInfo(@"Incoming file transfer failed because: %@", causeOfFailure); + + if (!error && causeOfFailure) { + NSDictionary *errInfo = @{NSLocalizedDescriptionKey : causeOfFailure}; + error = [NSError errorWithDomain:XMPPIncomingFileTransferErrorDomain + code:-1 + userInfo:errInfo]; + } + + _transferState = XMPPIFTStateNone; + [multicastDelegate xmppIncomingFileTransfer:self didFailWithError:error]; +} + +/** +* This method is called when the transfer is successfully completed. It +* handles resetting variables for another transfer and alerts the delegate of +* the transfer completion. +*/ +- (void)transferSuccess +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + [self cancelIBBTimer]; + + [self->multicastDelegate xmppIncomingFileTransfer:self + didSucceedWithData:self->_receivedData + named:self->_receivedFileName]; + [self cleanUp]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method is used to reset the system for receiving new files. +*/ +- (void)cleanUp +{ + XMPPLogTrace(); + + if (_asyncSocket) { + [_asyncSocket setDelegate:nil]; + [_asyncSocket disconnect]; + _asyncSocket = nil; + } + + _streamMethods &= 0; + _transferState = XMPPIFTStateNone; + _senderJID = nil; + _streamhostsQueryId = nil; + _streamhostUsed = nil; + _receivedData = nil; + _receivedFileName = nil; + _totalDataSize = 0; + _receivedDataSize = 0; +} + + +#pragma mark - Timeouts + +/** +* Resets the IBB timer that will cause the transfer to formally fail if an IBB +* data IQ stanza isn't received within the timeout. +*/ +- (void)resetIBBTimer:(NSTimeInterval)timeout +{ + NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked on incorrect queue."); + + if (_ibbTimer == NULL) { + _ibbTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue); + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC); + + dispatch_source_set_timer(_ibbTimer, tt, DISPATCH_TIME_FOREVER, 1); + dispatch_resume(_ibbTimer); + } else { + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC); + dispatch_source_set_timer(_ibbTimer, tt, DISPATCH_TIME_FOREVER, 1); + } + + dispatch_source_set_event_handler(_ibbTimer, ^{ + @autoreleasepool { + NSString *errMsg = @"The IBB transfer timed out. It's likely that the sender canceled the" + @" transfer or has gone offline."; + [self failWithReason:errMsg error:nil]; + } + }); +} + +- (void)cancelIBBTimer +{ + NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked on incorrect queue."); + + if (_ibbTimer) { + dispatch_source_cancel(_ibbTimer); +#if !OS_OBJECT_USE_OBJC + dispatch_release(_ibbTimer); + #endif + _ibbTimer = NULL; + } +} + + +#pragma mark - XMPPStreamDelegate + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq +{ + if (_transferState == XMPPIFTStateNone && [self isDiscoInfoIQ:iq]) { + [self sendIdentity:iq]; + _transferState = XMPPIFTStateWaitingForSIOffer; + return YES; + } + + if ((_transferState == XMPPIFTStateNone || _transferState == XMPPIFTStateWaitingForSIOffer) + && [self isSIOfferIQ:iq]) { + // Alert the delegate that we've received a stream initiation offer + [multicastDelegate xmppIncomingFileTransfer:self didReceiveSIOffer:iq]; + + if (_autoAcceptFileTransfers) { + [self sendSIOfferAcceptance:iq]; + } + + return YES; + } + + if (_transferState == XMPPIFTStateWaitingForStreamhosts && [self isStreamhostsListIQ:iq]) { + [self attemptStreamhostsConnection:iq]; + return YES; + } + + if (_transferState == XMPPIFTStateWaitingForIBBOpen && [self isIBBOpenRequestIQ:iq]) { + [self sendIBBAcceptance:iq]; + _transferState = XMPPIFTStateWaitingForIBBData; + + // Handle the scenario that the transfer is cancelled. + [self resetIBBTimer:20]; + return YES; + } + + if (_transferState == XMPPIFTStateWaitingForIBBData && [self isIBBDataIQ:iq]) { + [self processReceivedIBBDataIQ:iq]; + } + + return iq != nil; +} + + +#pragma mark - GCDAsyncSocketDelegate + +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port +{ + XMPPLogVerbose(@"%@: didConnectToHost:%@ port:%d", THIS_FILE, host, port); + + [self socks5WriteMethod]; + _transferState = XMPPIFTStateConnected; +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + XMPPLogVerbose(@"%@: didReadData:%@ withTag:%ld", THIS_FILE, data, tag); + + switch (tag) { + case SOCKS_TAG_READ_METHOD: + [self socks5ReadMethod:data]; + break; + case SOCKS_TAG_READ_REPLY: + [self socks5ReadReply:data]; + case SOCKS_TAG_READ_ADDRESS: + [_asyncSocket readDataToLength:_totalDataSize + withTimeout:TIMEOUT_READ + tag:SOCKS_TAG_READ_DATA]; + break; + case SOCKS_TAG_READ_DATA: + // Success! + _receivedData = [data mutableCopy]; + [self transferSuccess]; + default: + break; + } +} + +- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag +{ + XMPPLogVerbose(@"%@: didWriteDataWithTag:%ld", THIS_FILE, tag); + + switch (tag) { + case SOCKS_TAG_WRITE_METHOD: + [_asyncSocket readDataToLength:2 withTimeout:TIMEOUT_READ tag:SOCKS_TAG_READ_METHOD]; + break; + case SOCKS_TAG_WRITE_CONNECT: + [_asyncSocket readDataToLength:5 withTimeout:TIMEOUT_READ + tag:SOCKS_TAG_READ_REPLY]; + default: + break; + } +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + XMPPLogTrace(); + + if (_transferState == XMPPIFTStateConnected) { + [self failWithReason:@"Socket disconnected before transfer complete." error:nil]; + } +} + + +#pragma mark - SOCKS5 + +/** +* This method attempts a connection to each of the streamhosts provided until +* either a connection is established or there are no more streamhosts. In the +* latter case, an error stanza is sent to the sender. +* +* @see socket:didConnectToHost:port: +*/ +- (void)attemptStreamhostsConnection:(XMPPIQ *)iq +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + self->_streamhostsQueryId = iq.elementID; + self->_transferState = XMPPIFTStateConnectingToStreamhosts; + self->_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self->moduleQueue]; + + // Since we've already validated our IQ stanza, we can just pull the data + NSArray *streamhosts = [iq.childElement elementsForName:@"streamhost"]; + + for (NSXMLElement *streamhost in streamhosts) { + NSString *host = [streamhost attributeStringValueForName:@"host"]; + uint16_t port = [streamhost attributeUInt32ValueForName:@"port"]; + + NSError *err; + if (![self->_asyncSocket connectToHost:host onPort:port error:&err]) { + XMPPLogVerbose(@"%@: Unable to host:%@ port:%d error:%@", THIS_FILE, host, port, err); + continue; + } + + // If we make it this far, we've successfully connected to one of the hosts. + self->_streamhostUsed = [streamhost attributeStringValueForName:@"jid"]; + + return; + } + + // If we reach this, we weren't able to connect to any of the streamhosts. + // We'll send an error to the sender to let them know, and then we'll alert + // the delegate of the failure. + // + // XEP-0065 Example 13. + // + // + // + // + // + // + + XMPPIQ *errorIq = [XMPPIQ iqWithType:@"error" to:iq.from elementID:iq.elementID]; + + NSXMLElement *errorElem = [NSXMLElement elementWithName:@"error"]; + [errorElem addAttributeWithName:@"type" stringValue:@"modify"]; + + NSXMLElement *notAcceptable = [NSXMLElement elementWithName:@"not-acceptable" + xmlns:@"urn:ietf:params:xml:ns:xmpp-stanzas"]; + [errorElem addChild:notAcceptable]; + [errorIq addChild:errorElem]; + + [self->xmppStream sendElement:errorIq]; + + NSString *errMsg = @"Unable to connect to any of the provided streamhosts."; + [self failWithReason:errMsg error:nil]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5WriteMethod +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // We will attempt anonymous authentication with the proxy. The request is + // the same that we would read if this were a direct connection. The only + // difference is this time we initiate the request as a client rather than + // being a the 'server.' + // + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + // + // We're sending: + // + // VER = 5 (SOCKS5) + // NMETHODS = 1 (number of methods) + // METHODS = 0 (no authentication) + + void *byteBuf = malloc(3); + + UInt8 ver = 5; + memcpy(byteBuf, &ver, sizeof(ver)); + + UInt8 nmethods = 1; + memcpy(byteBuf + 1, &nmethods, sizeof(nmethods)); + + UInt8 methods = 0; + memcpy(byteBuf + 2, &methods, sizeof(methods)); + + NSData *data = [NSData dataWithBytesNoCopy:byteBuf length:3 freeWhenDone:YES]; + [self->_asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_METHOD]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5ReadMethod:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // We've sent a request to connect with no authentication. This is the + // response: + // + // +----+--------+ + // |VER | METHOD | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + // + // We're expecting: + // + // VER = 5 (SOCKS5) + // METHOD = 0 (no authentication) + + UInt8 version = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0]; + UInt8 method = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1]; + + if (version != 5 || method) { + [self failWithReason:@"Proxy doesn't allow anonymous authentication." error:nil]; + return; + } + + NSData *hash = [self sha1Hash]; + + // The SOCKS request is formed as follows: + // + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // We're sending: + // + // VER = 5 + // CMD = 1 (connect) + // RSV = 0 (reserved; this will always be 0) + // ATYP = 3 (domain name) + // DST.ADDR (varies based on ATYP) + // DST.PORT = 0 (according to XEP-0065) + // + // Immediately after ATYP, we need to send the length of our address. Because + // SHA1 is always 40 bytes, we simply send this value. After it, we append + // the actual hash and then the port. + + void *byteBuf = malloc(5 + 40 + 2); + + UInt8 ver = 5; + memcpy(byteBuf, &ver, sizeof(ver)); + + UInt8 cmd = 1; + memcpy(byteBuf + 1, &cmd, sizeof(cmd)); + + UInt8 rsv = 0; + memcpy(byteBuf + 2, &rsv, sizeof(rsv)); + + UInt8 atyp = 3; + memcpy(byteBuf + 3, &atyp, sizeof(atyp)); + + UInt8 hashlen = (UInt8) hash.length; + memcpy(byteBuf + 4, &hashlen, sizeof(hashlen)); + + memcpy(byteBuf + 5, hash.bytes, hashlen); + + UInt8 port = 0; + memcpy(byteBuf + 5 + hashlen, &port, sizeof(port)); + memcpy(byteBuf + 6 + hashlen, &port, sizeof(port)); + + NSData *data = [NSData dataWithBytesNoCopy:byteBuf length:47 freeWhenDone:YES]; + [self->_asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_CONNECT]; + + XMPPLogVerbose(@"%@: writing connect request: %@", THIS_FILE, data); + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5ReadReply:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // The server/sender will reply to our connect command with the following: + // + // +----+-----+-------+------+----------+----------+ + // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // VER = 5 (SOCKS5) + // REP = 0 (Success) + // RSV = 0 + // ATYP = 3 (Domain) - NOTE: Since we're using ATYP = 3, we must check the + // length of the server's host in the next byte. + + UInt8 ver = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0]; + UInt8 rep = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1]; + UInt8 atyp = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:3]; + UInt8 hostlen = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:4]; + + if (ver != 5 || rep || atyp != 3) { + [self failWithReason:@"Invalid VER, REP, or ATYP." error:nil]; + return; + } + + // According to XEP-0065 Example 23, we don't need to validate the + // address we were sent (at least that is how I interpret it), so we + // just read the next 42 bytes (hostlen + portlen) so there's no + // conflict when reading the data and then send to + // the file transfer initiator. Note that the sid must be included. + // + // XEP-0065 Example 17: + // + // + // + // + // + // + + XMPPIQ *iq = [XMPPIQ iqWithType:@"result" to:self->_senderJID elementID:self->_streamhostsQueryId]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPBytestreamsNamespace]; + [query addAttributeWithName:@"sid" stringValue:self.sid]; + + NSXMLElement *streamhostUsed = [NSXMLElement elementWithName:@"streamhost-used"]; + [streamhostUsed addAttributeWithName:@"jid" + stringValue:self->_streamhostUsed]; + + [query addChild:streamhostUsed]; + [iq addChild:query]; + + [self->xmppStream sendElement:iq]; + + // We're basically piping these to dev/null because we don't care. + // However, we need to tag this read so we can start to read the actual + // data once this read is finished. + [self->_asyncSocket readDataToLength:hostlen + 2 + withTimeout:TIMEOUT_READ + tag:SOCKS_TAG_READ_ADDRESS]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +@end diff --git a/Extensions/FileTransfer/XMPPOutgoingFileTransfer.h b/Extensions/FileTransfer/XMPPOutgoingFileTransfer.h new file mode 100644 index 0000000000..affddffdd8 --- /dev/null +++ b/Extensions/FileTransfer/XMPPOutgoingFileTransfer.h @@ -0,0 +1,140 @@ +// +// Created by Jonathon Staff on 10/21/14. +// Copyright (c) 2014 nplexity, LLC. All rights reserved. +// + +#import +#import "XMPPFileTransfer.h" + +@interface XMPPOutgoingFileTransfer : XMPPFileTransfer + +/** +* (Required) +* +* The data being sent to the recipient. +* +* If you're using startFileTransfer:, you *MUST* set this prior to calling +* startFileTransfer:. +*/ +@property (nonatomic, strong) NSData *outgoingData; + +/** +* (Required) +* +* The recipient of your file transfer. +* +* If you're using startFileTransfer:, you *MUST* set this prior to calling +* startFileTransfer:. +*/ +@property (nonatomic, strong) XMPPJID *recipientJID; + +/** +* (Optional) +* +* The name of the file you're sending. +* +* If you don't provide a filename, one will be generated for you. +*/ +@property (nonatomic, copy) NSString *outgoingFileName; + +/** +* (Optional) +* +* The description of the file you're sending. +*/ +@property (nonatomic, copy) NSString *outgoingFileDescription; + +/** +* (Optional) +* +* Specifies whether or not a random name should be generated instead of the +* filename provided. The randomly generated name will retain the same file +* extension as the original name if one was provided +* +* The default is NO; set to YES to generate a random name. +*/ +@property (nonatomic, assign) BOOL shouldGenerateRandomName; + +/** +* (Optional) +* +* Specifies the default block-size when using IBB file transfers. The default +* value is 4096 (Bytes). If the file recipient requests a smaller block-size, +* it will be halved. +*/ +@property (nonatomic, assign) int32_t blockSize; + + +#pragma mark - Public Methods + +/** +* Starts the file transfer. This assumes that at a minimum a recipientJID and +* outgoingData have already been provided. +* +* @param errPtr The address of an error which will be contain a description of +* the problem if there is one (optional). +* +* @return Returns NO if there is something blatantly wrong (not authorized, no +* recipientJID, no outgoingData); YES otherwise. +*/ +- (BOOL)startFileTransfer:(NSError **)errPtr; + +/** +* Sends the provided data to the provided recipient. Use of this method is not +* recommended, as there is no error handling, but you're free to make your own +* choices. +*/ +- (BOOL)sendData:(NSData *)data toRecipient:(XMPPJID *)recipient; + +/** +* Sends the provided data to the provided recipient. Pass nil for params you +* don't care about. +* +* @param data The data you wish to send (required). +* @param name The filename of the file you're sending (optional). +* @param recipient The recipient of your file transfer (required). Note that a +* resource must also be included in the JID. +* @param description The description of the file you're sending (optional). +* @param errPtr The address of an error which will contain a description of the +* problem if there is one (optional). +*/ +- (BOOL)sendData:(NSData *)data + named:(NSString *)name + toRecipient:(XMPPJID *)recipient + description:(NSString *)description + error:(NSError **)errPtr; + +@end + + +#pragma mark - XMPPOutgoingFileTransferDelegate + +@protocol XMPPOutgoingFileTransferDelegate +@optional + +/** +* Implement this method when calling startFileTransfer: or sendData:(variants). +* It will be invoked if the file transfer fails to execute properly. More +* information will be given in the error. +* +* @param sender XMPPOutgoingFileTransfer object invoking this delegate method. +* @param error NSError containing more details of the failure. +*/ +- (void)xmppOutgoingFileTransfer:(XMPPOutgoingFileTransfer *)sender + didFailWithError:(NSError *)error; + +/** +* Implement this method when calling startFileTransfer: or sendData:(variants). +* It will be invoked if the outgoing file transfer was completed successfully. +* +* @param sender XMPPOutgoingFileTransfer object invoking this delegate method. +*/ +- (void)xmppOutgoingFileTransferDidSucceed:(XMPPOutgoingFileTransfer *)sender; + +/** +* Not really sure why you would want this information, but hey, when I get +* information, I'm happy to share. +*/ +- (void)xmppOutgoingFileTransferIBBClosed:(XMPPOutgoingFileTransfer *)sender; + +@end diff --git a/Extensions/FileTransfer/XMPPOutgoingFileTransfer.m b/Extensions/FileTransfer/XMPPOutgoingFileTransfer.m new file mode 100644 index 0000000000..4333a62562 --- /dev/null +++ b/Extensions/FileTransfer/XMPPOutgoingFileTransfer.m @@ -0,0 +1,2187 @@ +// +// Created by Jonathon Staff on 10/21/14. +// Copyright (c) 2014 nplexity, LLC. All rights reserved. +// + +#if !__has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import +#import +#import "XMPPLogging.h" +#import "XMPPOutgoingFileTransfer.h" +#import "XMPPIDTracker.h" +#import "XMPPConstants.h" +#import "NSNumber+XMPP.h" +#import "NSData+XMPP.h" + +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // XMPP_LOG_LEVEL_VERBOSE | XMPP_LOG_FLAG_TRACE; +#else + static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +#define IOS_CELLULAR @"pdp_ip0" +#define IOS_WIFI @"en0" +#define IP_ADDR_IPv4 @"ipv4" +#define IP_ADDR_IPv6 @"ipv6" + +/** +* Seeing a return statements within an inner block +* can sometimes be mistaken for a return point of the enclosing method. +* This makes inline blocks a bit easier to read. +**/ +#define return_from_block return + +/** +* Tags for _asyncSocket handling. +*/ +#define SOCKS_TAG_READ_METHOD 101 +#define SOCKS_TAG_WRITE_METHOD 102 +#define SOCKS_TAG_READ_REQUEST 103 +#define SOCKS_TAG_READ_DOMAIN 104 +#define SOCKS_TAG_WRITE_REPLY 105 +#define SOCKS_TAG_WRITE_DATA 106 +#define SOCKS_TAG_WRITE_PROXY_METHOD 107 +#define SOCKS_TAG_READ_PROXY_METHOD 108 +#define SOCKS_TAG_WRITE_PROXY_CONNECT 109 +#define SOCKS_TAG_READ_PROXY_REPLY 110 + +#define TIMEOUT_WRITE -1 +#define TIMEOUT_READ 5.0 + +/** +* Set the default timeout for requests to be 60 seconds. +*/ +#define OUTGOING_DEFAULT_TIMEOUT 60 + +// XMPP Outgoing File Transfer State +typedef NS_ENUM(int, XMPPOFTState) { + XMPPOFTStateNone, + XMPPOFTStateStarted, + XMPPOFTStateSOCKSLive, + XMPPOFTStateConnectingToProxy, + XMPPOFTStateFinished +}; + +NSString *const XMPPOutgoingFileTransferErrorDomain = @"XMPPOutgoingFileTransferErrorDomain"; + +@interface XMPPOutgoingFileTransfer () { + dispatch_queue_t _outgoingQueue; + void *_outgoingQueueTag; + + NSString *_localIPAddress; + uint16_t _localPort; + + GCDAsyncSocket *_outgoingSocket; + + int32_t _outgoingDataBlockSeq; + NSUInteger _sentDataSize; + NSUInteger _totalDataSize; + NSString *_outgoingDataBase64; + + XMPPOFTState _transferState; + + XMPPJID *_proxyJID; + + NSMutableDictionary *_pastRecipients; +} + +@end + +@implementation XMPPOutgoingFileTransfer + + +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue +{ + self = [super initWithDispatchQueue:queue]; + if (self) { + // Create separate dispatch queue. + _outgoingQueue = dispatch_queue_create("XMPPOutgoingFileTransfer", NULL); + _outgoingQueueTag = &_outgoingQueueTag; + dispatch_queue_set_specific(_outgoingQueue, _outgoingQueueTag, _outgoingQueueTag, NULL); + + // define the default block-size in case we use IBB + _blockSize = 4096; + + _transferState = XMPPOFTStateNone; + _pastRecipients = [NSMutableDictionary new]; + } + return self; +} + + +#pragma mark - XMPPModule Methods + +- (void)didActivate +{ + XMPPLogTrace(); + + _idTracker = [[XMPPIDTracker alloc] initWithStream:xmppStream dispatchQueue:moduleQueue]; +} + +- (void)willDeactivate +{ + XMPPLogTrace(); + + [_idTracker removeAllIDs]; + _idTracker = nil; +} + +#pragma mark - Public Methods + +- (BOOL)startFileTransfer:(NSError **)errPtr +{ + XMPPLogTrace(); + + if (!xmppStream.isConnected) { + if (errPtr) { + NSString *errMsg = @"You must be connected to send a file"; + *errPtr = [self localErrorWithMessage:errMsg code:-1]; + } + + return NO; + } + + if (!_outgoingData) { + if (errPtr) { + NSString *errMsg = @"You must provide data to be sent."; + *errPtr = [self localErrorWithMessage:errMsg code:-1]; + } + + return NO; + } + + if (!_recipientJID || ![_recipientJID isFull]) { + if (errPtr) { + NSString *errMsg = @"You must provide a recipient (including a resource)."; + *errPtr = [self localErrorWithMessage:errMsg code:-1]; + } + + return NO; + } + + if (self.disableSOCKS5 && self.disableIBB) { + if (errPtr) { + NSString *errMsg = @"Both SOCKS5 and IBB transfers are disabled."; + *errPtr = [self localErrorWithMessage:errMsg code:-1]; + } + + return NO; + } + + if (_transferState != XMPPOFTStateNone) { + if (errPtr) { + NSString *errMsg = @"Transfer already in progress."; + *errPtr = [self localErrorWithMessage:errMsg code:-1]; + } + + return NO; + } + + dispatch_block_t block = ^{ + @autoreleasepool { + self->_transferState = XMPPOFTStateStarted; + + if (self->_pastRecipients[self->_recipientJID.full]) { + uint8_t methods = [self->_pastRecipients[self->_recipientJID.full] unsignedIntValue]; + + if (methods & XMPPFileTransferStreamMethodBytestreams) { + self->_streamMethods |= XMPPFileTransferStreamMethodBytestreams; + } + + if (methods & XMPPFileTransferStreamMethodIBB) { + self->_streamMethods |= XMPPFileTransferStreamMethodIBB; + } + + if (self->_streamMethods) { + [self querySIOffer]; + return_from_block; + } + } + + [self queryRecipientDiscoInfo]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); + + return YES; +} + +- (BOOL)sendData:(NSData *)data toRecipient:(XMPPJID *)recipient +{ + return _transferState != XMPPOFTStateNone ? NO : [self sendData:data + named:nil + toRecipient:recipient + description:nil + error:nil]; + +} + +- (BOOL)sendData:(NSData *)data + named:(NSString *)name + toRecipient:(XMPPJID *)recipient + description:(NSString *)description + error:(NSError **)errPtr +{ + if (_transferState != XMPPOFTStateNone) { + if (errPtr) { + NSString *errMsg = @"Transfer already in progress."; + *errPtr = [self localErrorWithMessage:errMsg code:-1]; + } + + return NO; + } + + self.outgoingData = data; + self.outgoingFileName = name; + self.recipientJID = recipient; + self.outgoingFileDescription = description; + + return [self startFileTransfer:errPtr]; +} + + +#pragma mark - Private Methods + +/** +* This method sends a `disco#info` query to the recipient. This is done to +* ensure they support file transfer, SOCKS5, and IBB. +* +* The request will look like the following: +* +* +* +* +* +* @see handleRecipientDiscoInfoQueryIQ:withInfo: +*/ +- (void)queryRecipientDiscoInfo +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" + to:self->_recipientJID + elementID:[self->xmppStream generateUUID]]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPDiscoInfoNamespace]; + [iq addChild:query]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleRecipientDiscoInfoQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method is responsible for sending the Stream Initiation Offer as +* described in Examples 1 and 3 of XEP-0096. Both SOCKS5 bytestreams (XEP-0065) +* and IBB (XEP-0047) are sent as options. The default, per XEP-0096 3.1, is +* SOCKS5, with IBB as the fallback. +* +* The outgoing IQ will be similar to the one below: +* +* +* +* +* We should destroy this, right? +* +* +* +* +* +* +* +* +* +* +* +* +* @see handleSIOfferQueryIQ:withInfo: +*/ +- (void)querySIOffer +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:self->_recipientJID + elementID:[self->xmppStream generateUUID]]; + [iq addAttributeWithName:@"from" stringValue:self->xmppStream.myJID.full]; + + // Store the sid; we'll need this later + self.sid = [self->xmppStream generateUUID]; + + NSXMLElement *si = [NSXMLElement elementWithName:@"si" xmlns:XMPPSINamespace]; + [si addAttributeWithName:@"id" stringValue:self.sid]; + [si addAttributeWithName:@"profile" stringValue:XMPPSIProfileFileTransferNamespace]; + [iq addChild:si]; + + // Generate a random filename if one isn't provided + NSString *fileName; + if (self->_outgoingFileName) { + + // If there is a name provided, but a random one should be created, we'll keep the file ext. + if (self->_shouldGenerateRandomName) { + NSString *ext = [[self->_outgoingFileName componentsSeparatedByString:@"."] lastObject]; + fileName = [NSString stringWithFormat:@"%@.%@", [self->xmppStream generateUUID], ext]; + } else { + fileName = self->_outgoingFileName; + } + } else { + fileName = [self->xmppStream generateUUID]; + } + + NSXMLElement *file = [NSXMLElement elementWithName:@"file" + xmlns:XMPPSIProfileFileTransferNamespace]; + [file addAttributeWithName:@"name" stringValue:fileName]; + [file addAttributeWithName:@"size" + stringValue:[[NSString alloc] initWithFormat:@"%lu", + (unsigned long) [self->_outgoingData length]]];//TODO + [si addChild:file]; + + // Only include description if it's provided + if (self->_outgoingFileDescription) { + NSXMLElement *desc = [NSXMLElement elementWithName:@"desc" + stringValue:self->_outgoingFileDescription]; + [file addChild:desc]; + } + + NSXMLElement *feature = [NSXMLElement elementWithName:@"feature" + xmlns:XMPPFeatureNegNamespace]; + [si addChild:feature]; + + NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; + [x addAttributeWithName:@"type" stringValue:@"form"]; + [feature addChild:x]; + + NSXMLElement *field = [NSXMLElement elementWithName:@"field"]; + [field addAttributeWithName:@"var" stringValue:@"stream-method"]; + [field addAttributeWithName:@"type" stringValue:@"list-single"]; + [x addChild:field]; + + // We support SOCKS5 + if (!self.disableSOCKS5) { + NSXMLElement *option = [NSXMLElement elementWithName:@"option"]; + [field addChild:option]; + NSXMLElement *value = [NSXMLElement elementWithName:@"value" + stringValue:XMPPBytestreamsNamespace]; + [option addChild:value]; + } + + // We support IBB + if (!self.disableIBB) { + NSXMLElement *option2 = [NSXMLElement elementWithName:@"option"]; + [field addChild:option2]; + NSXMLElement *value2 = [NSXMLElement elementWithName:@"value" + stringValue:XMPPIBBNamespace]; + [option2 addChild:value2]; + } + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleSIOfferQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method begins the process of collecting streamhosts to send to the +* recipient. The first (and preferred) streamhost is the sender's local +* IP Address and a random local port. If the recipient is able to connect +* using this streamhost, the bytestream should be directly between clients and +* not require the use of a proxy. +*/ +- (void)collectStreamHosts +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + self->_localIPAddress = [self getIPAddress:YES]; + + if (!self->_localPort) { + self->_localPort = [XMPPOutgoingFileTransfer getRandomPort]; + } + + self->_streamhosts = [NSMutableArray new]; + + // Don't send direct streamhost details if disabled. + if (!self.disableDirectTransfers) { + NSXMLElement *streamHost = [NSXMLElement elementWithName:@"streamhost"]; + [streamHost addAttributeWithName:@"jid" stringValue:self->xmppStream.myJID.full]; + [streamHost addAttributeWithName:@"host" stringValue:self->_localIPAddress]; + [streamHost addAttributeWithName:@"port" intValue:self->_localPort]; + [self->_streamhosts addObject:streamHost]; + } + + [self queryProxyDiscoItems]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method queries the server to determine what its services are, hopefully +* finding that one of them is a proxy. A `disco#items` query is sent to the +* domain of the file transfer initiator. +* +* @see handleProxyDiscoItemsQueryIQ:withInfo: +*/ +- (void)queryProxyDiscoItems +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + NSString *toStr = self->xmppStream.myJID.domain; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPDiscoItemsNamespace]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" + to:[XMPPJID jidWithString:toStr] + elementID:[self->xmppStream generateUUID] + child:query]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleProxyDiscoItemsQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method queries a JID directly to determine whether or not it is a proxy +* service. The provided JID will be in the form `subdomain.domain.com`, likely +* `proxy.domain.com`. This method will be called for each service found at the +* initiator's server until a proxy service is found or all services have been +* exhausted. +* +* @see handleProxyDiscoInfoQueryIQ:withInfo: +*/ +- (void)queryProxyDiscoInfoWithJID:(XMPPJID *)jid +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" + to:jid + elementID:[self->xmppStream generateUUID]]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPDiscoInfoNamespace]; + [iq addChild:query]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleProxyDiscoInfoQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method queries a JID directly to determine its address and port. It has +* already been established that the provided JID is indeed a proxy. We merely +* need to know how to connect. +* +* @see handleProxyAddressQueryIQ:withInfo: +*/ +- (void)queryProxyAddressWithJID:(XMPPJID *)jid +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" + to:jid + elementID:[self->xmppStream generateUUID]]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPBytestreamsNamespace]; + [iq addChild:query]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleProxyAddressQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method sends the list of streamhosts to the recipient and waits for a +* connection on one of them. +* +* One of these streamhosts will be a local IP address of the sender. If either +* or both of the parties are behind a Network Address Translation (NAT) device, +* this will not work (provided that they aren't on the same Local Area Network +* (LAN). In theory, this means that if both devices are on cellular data, they +* should be able to establish a direct connection. If one (or both) are on wifi, +* either a proxy streamhost will have to be used or IBB will have to be used. +*/ +- (void)sendStreamHostsAndWaitForConnection +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (self->_streamhosts.count < 1) { + NSString *errMsg = + [NSString stringWithFormat:@"Unable to send streamhosts to %@", self->_recipientJID.full]; + [self failWithReason:errMsg error:nil]; + return; + } + + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:self->_recipientJID + elementID:[self->xmppStream generateUUID]]; + [iq addAttributeWithName:@"xmlns" stringValue:@"jabber:client"]; + [iq addAttributeWithName:@"from" stringValue:self->xmppStream.myJID.full]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPBytestreamsNamespace]; + [query addAttributeWithName:@"sid" stringValue:self.sid]; + + for (NSXMLElement *streamhost in self->_streamhosts) { + [streamhost detach]; + [query addChild:streamhost]; + } + + [iq addChild:query]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleSentStreamhostsQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + // Send the list of streamhosts to the recipient + [self->xmppStream sendElement:iq]; + + // Create a socket to listen for a direct connection + if (!self->_asyncSocket) { + self->_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self + delegateQueue:self->moduleQueue]; + } + + NSError *error; + + if (![self->_asyncSocket acceptOnPort:self->_localPort error:&error]) { + NSString *errMsg = [NSString stringWithFormat:@"Failed to open port %d", self->_localPort]; + [self failWithReason:errMsg error:error]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + + +#pragma mark - IBB Transfer + +/** +* This method is responsible for opening a new In-Band Bytestream, as shown in +* XEP-0047 Example 1. We *MUST* send the same sid inside the stanza +* that was used in the SI offer. +* +* The outgoing IQ will be similar to the following: +* +* +* +* +* +* @see handleInitialIBBQueryIQ:withInfo: +*/ +- (void)beginIBBTransfer +{ + + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:self->_recipientJID + elementID:[self->xmppStream generateUUID]]; + + NSXMLElement *open = [NSXMLElement elementWithName:@"open" xmlns:XMPPIBBNamespace]; + [open addAttributeWithName:@"block-size" intValue:self->_blockSize]; + [open addAttributeWithName:@"sid" stringValue:self.sid]; + [open addAttributeWithName:@"stanza" stringValue:@"iq"]; + [iq addChild:open]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleInitialIBBQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + + // Convert our data to base64 for the IBB transmission + self->_outgoingDataBase64 = [self->_outgoingData base64EncodedStringWithOptions:0]; + self->_totalDataSize = self->_outgoingDataBase64.length; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method handles the response to a query matching Example 1. Initiator +* requests session (XEP-0047). +* +* @see beginIBBTransfer +* +* The possible responses are described in Examples 2-5 of XEP-0047. +*/ +- (void)handleInitialIBBQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for response to IBB intiation."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errType = [errorElem attributeStringValueForName:@"type"]; + + // Handle Example 3 and 5 + if ([errType isEqualToString:@"cancel"]) { + NSString *errMsg = [NSString stringWithFormat:@"Error initiating IBB: %@", + [errorElem childAtIndex:0].name]; + [self failWithReason:errMsg error:nil]; + return_from_block; + } + + // Handle Example 4. We'll divide the block-size by 4 and try again. + if ([errType isEqualToString:@"modify"] + && [[errorElem childAtIndex:0].name isEqualToString:@"resource-constraint"]) { + XMPPLogInfo(@"Responder prefers smaller IBB chunks. Shrinking block-size and retrying"); + self->_blockSize /= 2; + [self beginIBBTransfer]; + return_from_block; + } + } + + // Handle Example 2. Responder accepts session + if (iq.childCount == 0) { + XMPPLogVerbose(@"Responder has accepted IBB session. Begin sending data"); + [self sendIBBData]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* Sends the data to the recipient via IBB. This method will continue until it +* has verified that all the data has been sent, it fails, or receives an error +* from the recipient. It will close the IBB stream upon completion. +* +* Example 6. Sending data in an IQ stanza (XEP-0047) +* +* +* +* qANQR1DBwU4DX7jmYZnncmUQB/9KuKBddzQH+tZ1ZywKK0yHKnq57kWq+RFtQdCJ +* WpdWpR0uQsuJe7+vh3NWn59/gTc5MDlX8dS9p0ovStmNcyLhxVgmqS8ZKhsblVeu +* IpQ0JgavABqibJolc3BKrVtVV1igKiX/N7Pi8RtY1K18toaMDhdEfhBRzO/XB0+P +* AQhYlRjNacGcslkhXqNjK5Va4tuOAPy2n1Q8UUrHbUd0g+xJ9Bm0G0LZXyvCWyKH +* kuNEHFQiLuCY6Iv0myq6iX6tjuHehZlFSh80b5BVV9tNLwNR5Eqz1klxMhoghJOA +* +* +* +* @see handleIBBTransferQueryIQ:withInfo: +*/ +- (void)sendIBBData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (self->_sentDataSize < self->_totalDataSize) { + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:self->_recipientJID + elementID:[self->xmppStream generateUUID]]; + NSXMLElement *data = [NSXMLElement elementWithName:@"data" xmlns:XMPPIBBNamespace]; + [data addAttributeWithName:@"sid" stringValue:self.sid]; + [data addAttributeWithName:@"seq" intValue:self->_outgoingDataBlockSeq++]; + + // Get the base64 data for our block + NSUInteger length = self->_sentDataSize + self->_blockSize > self->_totalDataSize ? + self->_totalDataSize - self->_sentDataSize : self->_blockSize; + NSRange range = NSMakeRange(self->_sentDataSize, length); + + NSString *dataString = [self->_outgoingDataBase64 substringWithRange:range]; + XMPPLogVerbose(@"Uploading %lu/%lu bytes in IBB transfer.", (unsigned long) self->_sentDataSize, + (unsigned long) self->_totalDataSize); + + [data setStringValue:dataString]; + [iq addChild:data]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleIBBTransferQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } else { + XMPPLogInfo(@"IBB file transfer complete. Closing stream..."); + + // All the data has been sent. Alert the delegate that the transfer + // was successful and close the stream. + [self->multicastDelegate xmppOutgoingFileTransferDidSucceed:self]; + [self closeIBB]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* Handles the response from the data recipient during an IBB file transfer. The +* recipient should be sending back a childless result IQ confirming that they +* received the data we sent. We will wait until receiving this IQ before +* sending the next block of data. +* +* Example 7. Acknowledging data received via IQ (XEP-0047) +* +* +* +* @see sendIBBData +*/ +- (void)handleIBBTransferQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for response to IBB sent data."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + // Handle dropped connection or recipient offline. + if (errorElem) { + NSString *errMsg = [NSString stringWithFormat:@"Error transferring with IBB: %@", + [errorElem childAtIndex:0]]; + NSError *err = [self localErrorWithMessage:errMsg code:-1]; + + NSString *reason = + @"The recipient might be offline, the connection was interrupted, or the transfer was canceled."; + [self failWithReason:reason error:err]; + return; + } + + // Handle the scenario when the recipient closes the bytestream. + NSXMLElement *close = [iq elementForName:@"close"]; + if (close) { + if (self->_sentDataSize >= self->_totalDataSize) { + // We can assume the transfer was successful. + [self->multicastDelegate xmppOutgoingFileTransferDidSucceed:self]; + [self->multicastDelegate xmppOutgoingFileTransferIBBClosed:self]; + + // As per Examples 8-9 (XEP-0047), we SHOULD send the following + // response to let the other party know it's alright to close the + // bytestream. There's no reason to track it, however. + // + // + + XMPPIQ *resultIq = [XMPPIQ iqWithType:@"result" + to:self->_recipientJID + elementID:iq.elementID]; + [self->xmppStream sendElement:resultIq]; + } else { + // There must have been a reason to close, but we don't know it. + // Therefore, the transfer might not have been successful. + [self failWithReason:@"Recipient closed IBB stream." error:nil]; + [self->multicastDelegate xmppOutgoingFileTransferIBBClosed:self]; + } + } + + // At this point, we're assuming that we've received the stanza shown + // above and the recipient has successfully received the data we sent, + // so we should now send them the next block of data. + self->_sentDataSize += self->_blockSize; + [self sendIBBData]; + + XMPPLogVerbose( + @"Received response signifying successful IBB stanza. Sending the next block"); + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* XEP-0047 Example 8. Closing the bytestream +* +* Sends an IQ to the recipient stating that the bytestream will be closed. As +* per the protocol, we SHOULD wait for an IQ response before we can consider +* the bytestream to be closed. +* +* Note that the 'sid' must be included. +* +* @see handleCloseIBBQueryIQ:withInfo: +*/ +- (void)closeIBB +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:self->_recipientJID + elementID:[self->xmppStream generateUUID]]; + NSXMLElement *close = [NSXMLElement elementWithName:@"close" xmlns:XMPPIBBNamespace]; + [close addAttributeWithName:@"sid" stringValue:self.sid]; + [iq addChild:close]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleCloseIBBQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* Handles the response of our query to close the IBB. When it gets a response, +* it merely changes the state and logs that the stream is closed. a successful +* response will look like XEP-0047 Example 9. +*/ +- (void)handleCloseIBBQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for close IBB response."; + [self failWithReason:errMsg error:nil]; + } + + // The protocol states that we might receive an + // response, so we'll just ignore that here + NSXMLElement *errorElem = [iq elementForName:@"error"]; + if (errorElem && ![errorElem.name isEqualToString:@"item-not-found"]) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:errMsg error:err]; + return_from_block; + } + + // We're assuming that if it makes it this far, it's the response we want + [self->multicastDelegate xmppOutgoingFileTransferIBBClosed:self]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + + +#pragma mark - Response Handling + +/** +* This method handles the response of our `disco#info` query sent to the file +* recipient. We ensure that the recipient has the capabilities for our transfer +* before sending an SI offer. +* +* +* +* +* +* +* +* +* +* +*/ +- (void)handleRecipientDiscoInfoQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + XMPPLogInfo(@"iq: %@, info: %@", iq, info); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for recipient `disco#info` response."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:errMsg error:err]; + return_from_block; + } + + NSXMLElement *query = [iq elementForName:@"query"]; + + // We're checking to see if the recipient has the features we need + BOOL hasSI = NO; + BOOL hasFT = NO; + BOOL hasSOCKS5 = NO; + BOOL hasIBB = NO; + + NSArray *features = [query elementsForName:@"feature"]; + for (NSXMLElement *feature in features) { + NSString *var = [feature attributeStringValueForName:@"var"]; + if ([var isEqualToString:XMPPSINamespace]) hasSI = YES; + if ([var isEqualToString:XMPPSIProfileFileTransferNamespace]) hasFT = YES; + if ([var isEqualToString:XMPPBytestreamsNamespace]) hasSOCKS5 = YES; + if ([var isEqualToString:XMPPIBBNamespace]) hasIBB = YES; + } + + hasSOCKS5 = hasSI && hasFT && hasSOCKS5; + hasIBB = hasSI && hasFT && hasIBB; + + if (!hasSOCKS5 || !hasIBB) { + NSString *errMsg = + @"Unable to send SI offer; the recipient doesn't have the required features."; + XMPPLogInfo(@"%@: %@", THIS_FILE, errMsg); + + NSError *err = [self localErrorWithMessage:errMsg code:-1]; + [self->multicastDelegate xmppOutgoingFileTransfer:self didFailWithError:err]; + + return_from_block; + } + + [self querySIOffer]; + + // TODO: + // The following lines are currently useless. Maybe at some point I'll + // add the ability to restart the transfer using IBB if bytestreams + // fail, but only if the stream-method is available. + if (hasSOCKS5) { + self->_streamMethods |= XMPPFileTransferStreamMethodBytestreams; + } + + if (hasIBB) { + self->_streamMethods |= XMPPFileTransferStreamMethodIBB; + } + + self->_pastRecipients[self->_recipientJID.full] = @(self->_streamMethods); + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method is responsible for handling the response to the Stream Initiation +* Offer that will be in the form described in Examples 2 and 4 of XEP-0096. +* Depending on the response, this method will trigger an error or begin the +* transfer process using either SOCKS5 or IBB (whichever is sent back first). +* +* The response should be in a similar format to that which is shown below: +* +* +* +* +* +* +* http://jabber.org/protocol/bytestreams +* +* +* +* +* +*/ +- (void)handleSIOfferQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^void { + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for SI offer response."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:@"There was an issue with the SI offer." error:err]; + return_from_block; + } + + NSXMLElement *si = iq.childElement; + NSXMLElement *feature = (NSXMLElement *) [si childAtIndex:0]; + NSXMLElement *x = (NSXMLElement *) [feature childAtIndex:0]; + NSXMLElement *field = (NSXMLElement *) [x childAtIndex:0]; + NSXMLElement *value = (NSXMLElement *) [field childAtIndex:0]; + + if ([[value stringValue] isEqualToString:XMPPBytestreamsNamespace]) { + XMPPLogVerbose(@"The recipient has confirmed the use of SOCKS5. Starting transfer..."); + [self collectStreamHosts]; + } else if ([[value stringValue] isEqualToString:XMPPIBBNamespace]) { + XMPPLogVerbose(@"The recipient has confirmed the use of IBB. Beginning IBB transfer"); + [self beginIBBTransfer]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method handles the server's response to the `disco#items` query sent +* before. It iterates through the results and queries each JID to determine +* whether or not it is a proxy service. +* +* @see queryProxyDiscoInfoWithJID: +*/ +- (void)handleProxyDiscoItemsQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for proxy `disco#items` response."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:@"There was an error with the disco#items request." error:err]; + return_from_block; + } + + NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPDiscoItemsNamespace]; + if (!query) return; + + NSArray *items = [query elementsForName:@"item"]; + + for (NSXMLElement *item in items) { + XMPPJID *itemJid = [XMPPJID jidWithString:[item attributeStringValueForName:@"jid"]]; + + if (itemJid) { + XMPPLogVerbose(@"Found service %@. Querying to see if it's a proxy.", itemJid.full); + [self queryProxyDiscoInfoWithJID:itemJid]; + } + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method handles the server's response to the `disco#info` query sent +* before. It determines whether or not the service is indeed a proxy. If it is, +* the service is queried for its address and port. +* +* @see queryProxyAddressWithJID +*/ +- (void)handleProxyDiscoInfoQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for proxy `disco#info` response."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:@"There was an error with the disco#info request." error:err]; + return_from_block; + } + + NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPDiscoInfoNamespace]; + NSArray *identities = [query elementsForName:@"identity"]; + + for (NSXMLElement *identity in identities) { + NSString *category = [identity attributeStringValueForName:@"category"]; + NSString *type = [identity attributeStringValueForName:@"type"]; + + if ([category isEqualToString:@"proxy"] && [type isEqualToString:@"bytestreams"]) { + [self queryProxyAddressWithJID:iq.from]; + } + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method handles the server's response to the address query sent before. +* If there is no error, we assume that we were sent an address and a port in +* the form of a streamhost and send the streamhosts to the recipient to begin +* the actual connection process. +* +* @see sendStreamHostsAndWaitForConnection +*/ +- (void)handleProxyAddressQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for proxy address discovery response."; + [self failWithReason:errMsg error:nil]; + } + + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:@"There was an issue with the proxy address query." error:err]; + return_from_block; + } + + NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPBytestreamsNamespace]; + NSXMLElement *streamHost = [query elementForName:@"streamhost"]; + + if (!streamHost) { + [self failWithReason:@"There must be at least one streamhost." error:nil]; + return_from_block; + } + + // Detach the streamHost object so it can later be added to a query + [streamHost detach]; + [self->_streamhosts addObject:streamHost]; + + [self sendStreamHostsAndWaitForConnection]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method handles the server's response after sending the query of +* streamhosts. If there is an error, it alerts the delegate and causes the +* transfer to fail. Otherwise, the connection will proceed and the data will be +* written to the bytestream. +*/ +- (void)handleSentStreamhostsQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + NSXMLElement *errorElem = [iq elementForName:@"error"]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSError *err = [self localErrorWithMessage:errMsg + code:[errorElem attributeIntValueForName:@"code"]]; + [self failWithReason:@"There was an issue with sending the streamhosts." error:err]; + return_from_block; + } + + // Check for + // + // We're expecting something like: + // + // + // + // + // + // + + NSXMLElement *query = iq.childElement; + NSXMLElement *streamhostUsed = [query elementForName:@"streamhost-used"]; + + NSString *jid = [streamhostUsed attributeStringValueForName:@"jid"]; + XMPPLogVerbose(@"%@: streamhost-used received with jid: %@", THIS_FILE, jid); + + if ([jid isEqualToString:self->xmppStream.myJID.full]) { + XMPPLogVerbose(@"%@: writing data via direct connection.", THIS_FILE); + [self->_outgoingSocket writeData:self->_outgoingData + withTimeout:TIMEOUT_WRITE + tag:SOCKS_TAG_WRITE_DATA]; + return; + } + + XMPPLogVerbose(@"%@: unable use a direct connection; trying the provided streamhost.", + THIS_FILE); + + if (self->_outgoingSocket) { + if (self->_outgoingSocket.isConnected) { + [self->_outgoingSocket disconnect]; + } + self->_outgoingSocket = nil; + } + + // We need to get the streamhost which we discovered earlier as a proxy. + NSXMLElement *proxy; + for (NSXMLElement *streamhost in self->_streamhosts) { + if ([jid isEqualToString:[streamhost attributeStringValueForName:@"jid"]]) { + proxy = streamhost; + self->_proxyJID = [XMPPJID jidWithString:jid]; + break; + } + } + + if (self->_asyncSocket) { + [self->_asyncSocket setDelegate:nil]; + [self->_asyncSocket disconnect]; + } + + if (!self->_asyncSocket) { + self->_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self + delegateQueue:self->_outgoingQueue]; + } else { + [self->_asyncSocket setDelegate:self]; + } + + NSError *err; + NSString *proxyHost = [proxy attributeStringValueForName:@"host"]; + uint16_t proxyPort = [proxy attributeUnsignedIntegerValueForName:@"port"]; + + if (![self->_asyncSocket connectToHost:proxyHost onPort:proxyPort error:&err]) { + [self failWithReason:@"Unable to connect to proxy." error:err]; + return_from_block; + } + + self->_transferState = XMPPOFTStateConnectingToProxy; + // See the GCDAsyncSocket Delegate for the next steps. + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method handles the server's response after sending the IQ +* query as described in XEP-0065 Example 24. +* +* If the response is valid (Example 25), the actual transfer of data begins. +*/ +- (void)handleSentActivateQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + if (!iq) { + // If we're inside this block, it means that the timeout has been + // fired and we need to force a failure + NSString *errMsg = @"Timeout waiting for sent activate response."; + [self failWithReason:errMsg error:nil]; + } + + XMPPLogVerbose(@"Receive response to activate. Starting the actual data transfer now..."); + [self->_asyncSocket writeData:self->_outgoingData withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_DATA]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + + +#pragma mark - Util Methods + +- (NSError *)localErrorWithMessage:(NSString *)msg code:(NSInteger)code +{ + NSDictionary *errInfo = @{NSLocalizedDescriptionKey : [msg copy]}; + return [NSError errorWithDomain:XMPPOutgoingFileTransferErrorDomain + code:code + userInfo:errInfo]; +} + +- (NSString *)getIPAddress:(BOOL)preferIPv4 +{ + NSArray *searchArray; + + if (preferIPv4) { + searchArray = @[IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, + IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6]; + } else { + searchArray = @[IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, + IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4]; + } + + NSDictionary *addresses = [self getIPAddresses]; + + __block NSString *address; + [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) { + address = addresses[key]; + if (address) *stop = YES; + }]; + + return address; +} + +- (NSDictionary *)getIPAddresses +{ + NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; + + // Retrieve the current interfaces — returns 0 on success + struct ifaddrs *interfaces; + if (!getifaddrs(&interfaces)) { + // Loop through linked list of interfaces + struct ifaddrs *curr; + + for (curr = interfaces; curr; curr = curr->ifa_next) { + if (!(curr->ifa_flags & IFF_UP)) { + continue; + } + + const struct sockaddr_in *addr = (const struct sockaddr_in *) curr->ifa_addr; + char addr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; + + if (addr && (addr->sin_family == AF_INET || addr->sin_family == AF_INET6)) { + NSString *name = [NSString stringWithUTF8String:curr->ifa_name]; + NSString *type; + + if (addr->sin_family == AF_INET) { + if (inet_ntop(AF_INET, &addr->sin_addr, addr_buf, INET_ADDRSTRLEN)) { + type = IP_ADDR_IPv4; + } + } else { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) curr->ifa_addr; + + if (inet_ntop(AF_INET6, &addr6->sin6_addr, addr_buf, INET6_ADDRSTRLEN)) { + type = IP_ADDR_IPv6; + } + } + + if (type) { + NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; + addresses[key] = [NSString stringWithUTF8String:addr_buf]; + } + } + } + + freeifaddrs(interfaces); + } + + return addresses.count ? addresses : nil; +} + +/** +* Returns a random port number between 1024 and 49151, since these are the +* values available to use as ports. +*/ ++ (uint16_t)getRandomPort +{ + int port = arc4random_uniform(49151); + return (uint16_t) (port < 1024 ? port + 1024 : port); +} + +/** +* This method returns the SHA1 hash as per XEP-0065. +* +* The [address] MUST be SHA1(SID + Initiator JID + Target JID) and the output +* is hexadecimal encoded (not binary). +* +* Because this is an outgoing file transfer, we are always the initiator. +*/ +- (NSData *)sha1Hash +{ + NSString *hashMe = + [NSString stringWithFormat:@"%@%@%@", self.sid, xmppStream.myJID.full, _recipientJID.full]; + NSData *hashRaw = [[hashMe dataUsingEncoding:NSUTF8StringEncoding] xmpp_sha1Digest]; + NSData *hash = [[hashRaw xmpp_hexStringValue] dataUsingEncoding:NSUTF8StringEncoding]; + + XMPPLogVerbose(@"%@: hashMe : %@", THIS_FILE, hashMe); + XMPPLogVerbose(@"%@: hashRaw: %@", THIS_FILE, hashRaw); + XMPPLogVerbose(@"%@: hash : %@", THIS_FILE, hash); + + return hash; +} + +/** +* This method is called to clean up everything if the transfer fails. +*/ +- (void)failWithReason:(NSString *)causeOfFailure error:(NSError *)error +{ + XMPPLogTrace(); + XMPPLogInfo(@"Outgoing file transfer failed because: %@", causeOfFailure); + + if (!error && causeOfFailure) { + NSDictionary *errInfo = @{NSLocalizedDescriptionKey : causeOfFailure}; + error = [NSError errorWithDomain:XMPPOutgoingFileTransferErrorDomain + code:-1 + userInfo:errInfo]; + } + + [self cleanUp]; + [multicastDelegate xmppOutgoingFileTransfer:self didFailWithError:error]; +} + +/** +* This method is called to clean up everything if the transfer succeeds. +*/ +- (void)transferSuccess +{ + + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + self->_transferState = XMPPOFTStateFinished; + [self->multicastDelegate xmppOutgoingFileTransferDidSucceed:self]; + [self cleanUp]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +/** +* This method is used to reset the system for receiving new files. +*/ +- (void)cleanUp +{ + XMPPLogTrace(); + + if (_asyncSocket) { + [_asyncSocket setDelegate:nil]; + [_asyncSocket disconnect]; + _asyncSocket = nil; + } + + if (_outgoingSocket) { + [_outgoingSocket setDelegate:nil]; + [_outgoingSocket disconnect]; + _outgoingSocket = nil; + } + + _streamMethods &= 0; + _transferState = XMPPOFTStateNone; + _totalDataSize = 0; + _outgoingDataBlockSeq = 0; + _sentDataSize = 0; + _outgoingDataBase64 = nil; +} + + +#pragma mark - XMPPStreamDelegate + +/** +* Default XMPPStreamDelegate method. We need this to handle the IQ responses. +*/ +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq +{ + NSString *type = iq.type; + + if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]) { + return [_idTracker invokeForElement:iq withObject:iq]; + } + + return NO; +} + + +#pragma mark - GCDAsyncSocketDelegate + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + XMPPLogVerbose(@"Did accept new socket"); + XMPPLogVerbose(@"connected host: %@", newSocket.connectedHost); + XMPPLogVerbose(@"connected port: %hu", newSocket.connectedPort); + + _outgoingSocket = newSocket; + [_outgoingSocket readDataToLength:3 withTimeout:20 tag:SOCKS_TAG_READ_METHOD]; +} + +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port +{ + XMPPLogVerbose(@"%@: didConnectToHost:%@ port:%d", THIS_FILE, host, port); + + [self socks5WriteProxyMethod]; +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +{ + XMPPLogVerbose(@"%@: didReadData:%@ withTag:%ld", THIS_FILE, data, tag); + + switch (tag) { + case SOCKS_TAG_READ_METHOD: + [self socks5ReadMethod:data]; + break; + case SOCKS_TAG_READ_REQUEST: + [self socks5ReadRequest:data]; + break; + case SOCKS_TAG_READ_DOMAIN: + [self socks5ReadDomain:data]; + break; + case SOCKS_TAG_READ_PROXY_METHOD: + [self socks5ReadProxyMethod:data]; + break; + case SOCKS_TAG_READ_PROXY_REPLY: + [self socks5ReadProxyReply:data]; + break; + default: + break; + } +} + +- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag +{ + XMPPLogVerbose(@"%@: didWriteDataWithTag:%ld", THIS_FILE, tag); + + switch (tag) { + case SOCKS_TAG_WRITE_METHOD: + [_outgoingSocket readDataToLength:4 withTimeout:TIMEOUT_READ tag:SOCKS_TAG_READ_REQUEST]; + break; + case SOCKS_TAG_WRITE_PROXY_METHOD: + [_asyncSocket readDataToLength:2 withTimeout:TIMEOUT_READ tag:SOCKS_TAG_READ_PROXY_METHOD]; + break; + case SOCKS_TAG_WRITE_PROXY_CONNECT: + [_asyncSocket readDataToLength:5 withTimeout:TIMEOUT_READ tag:SOCKS_TAG_READ_PROXY_REPLY]; + break; + case SOCKS_TAG_WRITE_DATA: + [self transferSuccess]; + break; + default: + break; + } +} + +- (NSTimeInterval)socket:(GCDAsyncSocket *)sock +shouldTimeoutReadWithTag:(long)tag + elapsed:(NSTimeInterval)elapsed + bytesDone:(NSUInteger)length +{ + XMPPLogVerbose(@"%@: socket shouldTimeoutReadWithTag:%ld elapsed:%f bytesDone:%lu", THIS_FILE, tag, elapsed, (unsigned long)length); + + NSString *reason = [NSString stringWithFormat:@"Read timeout. %lu bytes read.", (unsigned long)length]; + [self failWithReason:reason error:nil]; + + return 0; +} + +- (NSTimeInterval) socket:(GCDAsyncSocket *)sock +shouldTimeoutWriteWithTag:(long)tag + elapsed:(NSTimeInterval)elapsed + bytesDone:(NSUInteger)length +{ + XMPPLogVerbose(@"%@: socket shouldTimeoutWriteWithTag:%ld elapsed:%f bytesDone:%lu", THIS_FILE, + tag, elapsed, (unsigned long) length); + + NSString *reason = [NSString stringWithFormat:@"Write timeout. %lu bytes written.", + (unsigned long) length]; + [self failWithReason:reason error:nil]; + + return 0; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + XMPPLogVerbose(@"socket did disconnect with error: %@", err); + if (_transferState != XMPPOFTStateFinished && _transferState != XMPPOFTStateNone) { + [self failWithReason:@"Socket disconnected before transfer completion." error:err]; + } +} + + +#pragma mark - SOCKS5 + +- (void)socks5ReadMethod:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // According to the SOCKS5 protocol (http://tools.ietf.org/html/rfc1928), + // we facilitate the role of the 'server' in this scenario, meaning that + // the 'client' has connected to us and written data in the following form: + // + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + // + // The VER field should always be set to 5 (for SOCKS v5). + // NMETHODS will always be a single byte and since we really only want this + // to be 1, we're free to ignore it. + // METHODS can have various values, but if it's set to anything other than + // 0 (no authentication), we're going to abort the process. + // + // We're thus expecting: + // + // VER = 5 + // NMETHODS = 1 + // METHODS = 0 + + UInt8 version = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0]; + UInt8 method = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:2]; + + if (version == 5 && method == 0) { + + // At this point, we've determined that no authentication is required and + // are able to proceed. In order to do so, we need to write data in the + // following form: + // + // +----+--------+ + // |VER | METHOD | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + // + // The VER will once again be set to 5, and METHOD will be set to 0. + // + // We're sending: + // + // VER = 5 + // METHOD = 0 + + void *byteBuf = malloc(2); + + UInt8 ver = 5; + memcpy(byteBuf, &ver, sizeof(ver)); + + UInt8 mtd = 0; + memcpy(byteBuf + 1, &mtd, sizeof(mtd)); + + NSData *responseData = [NSData dataWithBytesNoCopy:byteBuf length:2 freeWhenDone:YES]; + XMPPLogVerbose(@"%@: writing SOCKS5 auth response: %@", THIS_FILE, responseData); + + [self->_outgoingSocket writeData:responseData + withTimeout:TIMEOUT_WRITE + tag:SOCKS_TAG_WRITE_METHOD]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5ReadRequest:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // The SOCKS request is formed as follows: + // + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // We're expecting: + // + // VER = 5 + // CMD = 1 (connect) + // RSV = 0 (reserved; this will always be 0) + // ATYP = 1 (IPv4), 3 (domain name), or 4 (IPv6) + // DST.ADDR (varies based on ATYP) + // DST.PORT = 0 (according to XEP-0065) + // + // At this stage, we've only actually read 4 bytes from the stream, those + // being VER, CMD, RSV, and ATYP. We need to read ATYP to determine how many + // more bytes we should read. Scenarios listed below: + // + // ATYP = 3 (domain name): Read the next byte which will contain the number + // bytes in the address. Then read that many bytes + + // 2 for the port. Since this is the only type of + // ATYP we want to support, any other fails. We'll go + // ahead and read the whole address and port. It + // should always be 40 bytes long (SHA1). + + UInt8 ver = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0]; + UInt8 cmd = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1]; + UInt8 atyp = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:3]; + + if (ver != 5 || cmd != 1) { + [self failWithReason:@"Incorrect SOCKS version or command is not 'CONNECT'." error:nil]; + return; + } + + // Read the length byte + the 40-byte SHA1 + 2-byte address + NSUInteger length = 43; + if (atyp == 3) { + [self->_outgoingSocket readDataToLength:length + withTimeout:TIMEOUT_READ + tag:SOCKS_TAG_READ_DOMAIN]; + } else { + [self failWithReason:@"ATYP value is invalid." error:nil]; + } + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5ReadDomain:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + NSData *hash = [self sha1Hash]; + + // We need to pull the address data out, which starts after the first byte + // and goes for 40 bytes. + NSRange addrRange = NSMakeRange(1, 40); + if (![hash isEqualToData:[incomingData subdataWithRange:addrRange]]) { + XMPPLogVerbose(@"Addresses don't match. Canceling the SOCKS5 transfer."); + [self failWithReason:@"Addresses don't match." error:nil]; + return; + } + + // We need to next pull the port out and verify that it's 0x00, 0x00. + UInt8 addrPort0 = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:41]; + UInt8 addrPort1 = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:41]; + + if (addrPort0 || addrPort1) { + XMPPLogVerbose(@"Port should always be 0x00. Canceling the SOCKS5 transfer."); + [self failWithReason:@"Port isn't 0x00." error:nil]; + return; + } + + // If the DST.ADDR and DST.PORT are valid, then we proceed with the process. + // We send our reply which is described below. + // + // +----+-----+-------+------+----------+----------+ + // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // VER = 5 (SOCKS5) + // REP = 0 (Success) + // RSV = 0 + // ATYP = 3 (Domain) - NOTE: Since we're using ATYP = 3, we must send the + // length of our host in the very next byte. + // BND.ADDR = local IP address + // BND.PORT = 0x00 + // 0x00 + + const char *host = [self->_localIPAddress UTF8String]; + + NSUInteger numBytes = 5 + strlen(host) + 2; + + void *byteBuf = malloc(numBytes); + + UInt8 ver = 5; + memcpy(byteBuf, &ver, sizeof(ver)); + + UInt8 rep = 0; + memcpy(byteBuf + 1, &rep, sizeof(rep)); + + UInt8 rsv = 0; + memcpy(byteBuf + 2, &rsv, sizeof(rsv)); + + UInt8 atyp = 3; + memcpy(byteBuf + 3, &atyp, sizeof(atyp)); + + UInt8 hostlen = (UInt8) strlen(host); + memcpy(byteBuf + 4, &hostlen, sizeof(hostlen)); + + memcpy(byteBuf + 5, host, hostlen); + + UInt8 port = 0; + memcpy(byteBuf + 5 + hostlen, &port, sizeof(port)); + memcpy(byteBuf + 6 + hostlen, &port, sizeof(port)); + + NSData + *responseData = [NSData dataWithBytesNoCopy:byteBuf length:numBytes freeWhenDone:YES]; + XMPPLogVerbose(@"%@: writing SOCKS5 auth response: %@", THIS_FILE, responseData); + + [self->_outgoingSocket writeData:responseData + withTimeout:TIMEOUT_WRITE + tag:SOCKS_TAG_WRITE_REPLY]; + + self->_transferState = XMPPOFTStateSOCKSLive; + + // Now we wait for a IQ stanza before sending the data. + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5WriteProxyMethod +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // We will attempt anonymous authentication with the proxy. The request is + // the same that we would read if this were a direct connection. The only + // difference is this time we initiate the request as a client rather than + // being a the 'server.' + // + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + // + // We're sending: + // + // VER = 5 (SOCKS5) + // NMETHODS = 1 (number of methods) + // METHODS = 0 (no authentication) + + void *byteBuf = malloc(3); + + UInt8 ver = 5; + memcpy(byteBuf, &ver, sizeof(ver)); + + UInt8 nmethods = 1; + memcpy(byteBuf + 1, &nmethods, sizeof(nmethods)); + + UInt8 methods = 0; + memcpy(byteBuf + 2, &methods, sizeof(methods)); + + NSData *data = [NSData dataWithBytesNoCopy:byteBuf length:3 freeWhenDone:YES]; + [self->_asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_PROXY_METHOD]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5ReadProxyMethod:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // We've sent a request to connect with no authentication. This data contains + // the proxy server's response to our request. + // + // +----+--------+ + // |VER | METHOD | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + // + // We're expecting: + // + // VER = 5 (SOCKS5) + // METHOD = 0 (no authentication) + + UInt8 version = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0]; + UInt8 method = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1]; + + if (version != 5 || method) { + [self failWithReason:@"Proxy doesn't allow anonymous authentication." error:nil]; + return; + } + + NSData *hash = [self sha1Hash]; + + // The SOCKS request is formed as follows: + // + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // We're sending: + // + // VER = 5 + // CMD = 1 (connect) + // RSV = 0 (reserved; this will always be 0) + // ATYP = 3 (domain name) + // DST.ADDR (varies based on ATYP) + // DST.PORT = 0 (according to XEP-0065) + // + // Immediately after ATYP, we need to send the length of our address. Because + // SHA1 is always 40 bytes, we simply send this value. After it, we append + // the actual hash and then the port. + + void *byteBuf = malloc(5 + 40 + 2); + + UInt8 ver = 5; + memcpy(byteBuf, &ver, sizeof(ver)); + + UInt8 cmd = 1; + memcpy(byteBuf + 1, &cmd, sizeof(cmd)); + + UInt8 rsv = 0; + memcpy(byteBuf + 2, &rsv, sizeof(rsv)); + + UInt8 atyp = 3; + memcpy(byteBuf + 3, &atyp, sizeof(atyp)); + + UInt8 hashlen = (UInt8) hash.length; + memcpy(byteBuf + 4, &hashlen, sizeof(hashlen)); + + memcpy(byteBuf + 5, hash.bytes, hashlen); + + UInt8 port = 0; + memcpy(byteBuf + 5 + hashlen, &port, sizeof(port)); + memcpy(byteBuf + 6 + hashlen, &port, sizeof(port)); + + NSData *data = [NSData dataWithBytesNoCopy:byteBuf length:47 freeWhenDone:YES]; + [self->_asyncSocket writeData:data withTimeout:TIMEOUT_WRITE tag:SOCKS_TAG_WRITE_PROXY_CONNECT]; + + XMPPLogVerbose(@"%@: writing connect request: %@", THIS_FILE, data); + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)socks5ReadProxyReply:(NSData *)incomingData +{ + XMPPLogTrace(); + + dispatch_block_t block = ^{ + @autoreleasepool { + // The server will reply to our connect command with the following: + // + // +----+-----+-------+------+----------+----------+ + // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + // + // VER = 5 (SOCKS5) + // REP = 0 (Success) + // RSV = 0 + // ATYP = 3 (Domain) - NOTE: Since we're using ATYP = 3, we must check the + // length of the server's host in the next byte. + + UInt8 ver = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:0]; + UInt8 rep = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:1]; + UInt8 atyp = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:3]; + UInt8 hostlen = [NSNumber xmpp_extractUInt8FromData:incomingData atOffset:4]; + + if (ver != 5 || rep || atyp != 3) { + [self failWithReason:@"Invalid VER, REP, or ATYP." error:nil]; + return; + } + + // Read those bytes off into oblivion... + [self->_asyncSocket readDataToLength:hostlen + 2 withTimeout:TIMEOUT_READ tag:-1]; + + // According to XEP-0065 Example 23, we don't need to validate the + // address we were sent (at least that is how I interpret it), so we + // can just go ahead and send the IQ query and start + // sending the data once we receive our response. + + NSXMLElement *activate = [NSXMLElement elementWithName:@"activate" + stringValue:self->_recipientJID.full]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:@"http://jabber.org/protocol/bytestreams"]; + [query addAttributeWithName:@"sid" stringValue:self.sid]; + [query addChild:activate]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:self->_proxyJID + elementID:[self->xmppStream generateUUID] + child:query]; + + [self->_idTracker addElement:iq + target:self + selector:@selector(handleSentActivateQueryIQ:withInfo:) + timeout:OUTGOING_DEFAULT_TIMEOUT]; + + [self->xmppStream sendElement:iq]; + } + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +@end diff --git a/Extensions/GoogleSharedStatus/XMPPGoogleSharedStatus.m b/Extensions/GoogleSharedStatus/XMPPGoogleSharedStatus.m index eea122206f..21a07680fa 100644 --- a/Extensions/GoogleSharedStatus/XMPPGoogleSharedStatus.m +++ b/Extensions/GoogleSharedStatus/XMPPGoogleSharedStatus.m @@ -119,44 +119,44 @@ - (void)updateSharedStatus:(NSString *)status show:(NSString *)show invisible:(B status = [status substringToIndex:_statusMessageMaxLength]; // If the show has changed update it. - if(![show isEqualToString:[sharedStatusUpdate objectForKey:XMPPGoogleSharedStatusShow]]) { + if(![show isEqualToString:sharedStatusUpdate[XMPPGoogleSharedStatusShow]]) { [sharedStatusUpdate removeObjectForKey:XMPPGoogleSharedStatusShow]; - [sharedStatusUpdate setObject:show forKey:XMPPGoogleSharedStatusShow]; + sharedStatusUpdate[XMPPGoogleSharedStatusShow] = show; } // Get the current show value, and whether the status should update for it. BOOL displayShow = ([show isEqualToString:XMPPGoogleSharedStatusShowAvailable] || [show isEqualToString:XMPPGoogleSharedStatusShowBusy]); - NSString *currentShow = [self.sharedStatus objectForKey:XMPPGoogleSharedStatusShow]; + NSString *currentShow = self.sharedStatus[XMPPGoogleSharedStatusShow]; // Since we can't show an away status, only change the status it has // changed and is not showing idle. - if(![status isEqualToString:[sharedStatusUpdate objectForKey:XMPPGoogleSharedStatusStatus]] && displayShow) { + if(![status isEqualToString:sharedStatusUpdate[XMPPGoogleSharedStatusStatus]] && displayShow) { [sharedStatusUpdate removeObjectForKey:XMPPGoogleSharedStatusStatus]; - [sharedStatusUpdate setObject:status forKey:XMPPGoogleSharedStatusStatus]; + sharedStatusUpdate[XMPPGoogleSharedStatusStatus] = status; // Update the appropriate status list by adding the new status. // If the list max count was reached, truncate its earliest status. - NSMutableArray *statusList = [[sharedStatusUpdate objectForKey:currentShow] mutableCopy]; + NSMutableArray *statusList = [sharedStatusUpdate[currentShow] mutableCopy]; [statusList insertObject:status atIndex:0]; if(statusList.count > _statusListMaxCount) [statusList removeObjectAtIndex:statusList.count-1]; // Remove any blank statuses. for(int i = 0; i < statusList.count; i++) { - NSString *status = [statusList objectAtIndex:i]; + NSString *status = statusList[i]; if([status isEqualToString:@""]) [statusList removeObjectAtIndex:i]; } // Update the status list for this status. [sharedStatusUpdate removeObjectForKey:currentShow]; - [sharedStatusUpdate setObject:statusList forKey:currentShow]; + sharedStatusUpdate[currentShow] = statusList; } // Update the invisibility for this shared status. [sharedStatusUpdate removeObjectForKey:XMPPGoogleSharedStatusInvisible]; - [sharedStatusUpdate setObject:[NSNumber numberWithBool:invisible] forKey:XMPPGoogleSharedStatusInvisible]; + sharedStatusUpdate[XMPPGoogleSharedStatusInvisible] = @(invisible); // Wrap it in an XMPPIQ "set" and send it to the server. XMPPIQ *statusIQ = [XMPPIQ iqWithType:@"set" to:self.xmppStream.myJID.bareJID]; @@ -211,12 +211,12 @@ - (void)refreshSharedStatus { // information attributes as well, so save those. // If we received a discovery IQ, determine shared status support. - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { - XMPPElement *query = (XMPPElement *)[[iq children] objectAtIndex:0]; + XMPPElement *query = (XMPPElement *) [iq children][0]; if([query.xmlns isEqualToString:GOOGLE_SHARED_STATUS] && self.sharedStatusSupported) { self.sharedStatus = [self unpackSharedStatus:query]; - _show = [self.sharedStatus objectForKey:XMPPGoogleSharedStatusShow]; - _status = [self.sharedStatus objectForKey:XMPPGoogleSharedStatusStatus]; - _invisible = [[self.sharedStatus objectForKey:XMPPGoogleSharedStatusInvisible] boolValue]; + _show = self.sharedStatus[XMPPGoogleSharedStatusShow]; + _status = self.sharedStatus[XMPPGoogleSharedStatusStatus]; + _invisible = [self.sharedStatus[XMPPGoogleSharedStatusInvisible] boolValue]; [multicastDelegate xmppGoogleSharedStatus:self didReceiveUpdatedStatus:self.sharedStatus]; if([query attributeForName:@"status-max"]) @@ -247,14 +247,13 @@ - (NSDictionary *)unpackSharedStatus:(XMPPElement *)sharedStatus { NSMutableArray *array = [NSMutableArray array]; for(XMPPElement *status in element.children) [array addObject:[status stringValue]]; - [dict setObject:array forKey:[[element attributeForName:XMPPGoogleSharedStatusShow] stringValue]]; + dict[[[element attributeForName:XMPPGoogleSharedStatusShow] stringValue]] = array; } else if([element.name isEqualToString:XMPPGoogleSharedStatusStatus]) { - [dict setObject:[element stringValue] forKey:element.name]; + dict[element.name] = [element stringValue]; } else if([element.name isEqualToString:XMPPGoogleSharedStatusShow]) { - [dict setObject:[element stringValue] forKey:element.name]; + dict[element.name] = [element stringValue]; } else if([element.name isEqualToString:XMPPGoogleSharedStatusInvisible]) { - [dict setObject:[NSNumber numberWithBool:[[[element attributeForName:@"value"] stringValue] boolValue]] - forKey:element.name]; + dict[element.name] = @([[[element attributeForName:@"value"] stringValue] boolValue]); } else { NSLog(@"Missed element: %@", element); } @@ -273,15 +272,15 @@ - (XMPPElement *)packSharedStatus:(NSDictionary *)sharedStatus { [element addAttributeWithName:@"version" stringValue:@"2"]; [element addChild:[XMPPElement elementWithName:XMPPGoogleSharedStatusStatus - stringValue:[sharedStatus objectForKey:XMPPGoogleSharedStatusStatus]]]; + stringValue:sharedStatus[XMPPGoogleSharedStatusStatus]]]; [element addChild:[XMPPElement elementWithName:XMPPGoogleSharedStatusShow - stringValue:[sharedStatus objectForKey:XMPPGoogleSharedStatusShow]]]; + stringValue:sharedStatus[XMPPGoogleSharedStatusShow]]]; for(NSString *key in sharedStatus.allKeys.reverseObjectEnumerator) { if([key isEqualToString:XMPPGoogleSharedStatusShowAvailable] || [key isEqualToString:XMPPGoogleSharedStatusShowBusy]) { - NSArray *statusList = [sharedStatus objectForKey:key]; + NSArray *statusList = sharedStatus[key]; XMPPElement *statusElement = [XMPPElement elementWithName:@"status-list"]; [statusElement addAttributeWithName:@"show" stringValue:key]; diff --git a/Extensions/OMEMO/NSXMLElement+OMEMO.h b/Extensions/OMEMO/NSXMLElement+OMEMO.h new file mode 100644 index 0000000000..3bb2afac1d --- /dev/null +++ b/Extensions/OMEMO/NSXMLElement+OMEMO.h @@ -0,0 +1,64 @@ +// +// NSXMLElement+OMEMO.h +// XMPPFramework +// +// Created by Chris Ballinger on 9/20/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import +#import "NSXMLElement+XMPP.h" +#import "OMEMOModule.h" + +NS_ASSUME_NONNULL_BEGIN +@interface NSXMLElement (OMEMO) + +/** If element contains */ +- (BOOL) omemo_hasEncryptedElement:(OMEMOModuleNamespace)ns; +/** If element IS */ +- (BOOL) omemo_isEncryptedElement:(OMEMOModuleNamespace)ns; +/** Child element */ +- (nullable NSXMLElement*) omemo_encryptedElement:(OMEMOModuleNamespace)ns; + + +/** The Device ID is a randomly generated integer between 1 and 2^31 - 1. If zero it means the element was not found. Only works within element.
*/ +- (uint32_t) omemo_senderDeviceId; +/** key data is keyed to receiver deviceIds. Only works within element. BASE64ENCODED... .. */ +- (nullable NSArray*) omemo_keyData; +/** Only works within element. BASE64ENCODED */ +- (nullable NSData*) omemo_payload; +/** Encryption IV. Only works within element. BASE64ENCODED */ +- (nullable NSData*) omemo_iv; + + +/** + * The client may wish to transmit keying material to the contact. This first has to be generated. The client MUST generate a fresh, randomly generated key/IV pair. For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a KeyTransportElement, omitting the . + + +
+ BASE64ENCODED... + BASE64ENCODED... + + BASE64ENCODED... +
+
+ */ + ++ (NSXMLElement*) omemo_keyTransportElementWithKeyData:(NSArray*)keyData + iv:(NSData*)iv + senderDeviceId:(uint32_t)senderDeviceId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + + +/** Extracts device list from PEP element */ +- (nullable NSArray*)omemo_deviceListFromItems:(OMEMOModuleNamespace)ns; +/** Extracts device list from PEP iq respnse */ +- (nullable NSArray*)omemo_deviceListFromIqResponse:(OMEMOModuleNamespace)ns; + + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/NSXMLElement+OMEMO.m b/Extensions/OMEMO/NSXMLElement+OMEMO.m new file mode 100644 index 0000000000..84e4ea7f30 --- /dev/null +++ b/Extensions/OMEMO/NSXMLElement+OMEMO.m @@ -0,0 +1,152 @@ +// +// NSXMLElement+OMEMO.m +// XMPPFramework +// +// Created by Chris Ballinger on 9/20/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "NSXMLElement+OMEMO.h" +#import "XMPPIQ+XEP_0060.h" +#import "OMEMOModule.h" + +@implementation NSXMLElement (OMEMO) + +/** If element contains */ +- (BOOL) omemo_hasEncryptedElement:(OMEMOModuleNamespace)ns { + return [self omemo_encryptedElement:ns] != nil; +} + +/** If element IS */ +- (BOOL) omemo_isEncryptedElement:(OMEMOModuleNamespace)ns { + return [[self name] isEqualToString:@"encrypted"] && [[self xmlns] isEqualToString:[OMEMOModule xmlnsOMEMO:ns]]; +} + +/** Child element */ +- (nullable NSXMLElement*) omemo_encryptedElement:(OMEMOModuleNamespace)ns { + return [self elementForName:@"encrypted" xmlns:[OMEMOModule xmlnsOMEMO:ns]]; +} + +- (NSXMLElement*) omemo_headerElement { + return [self elementForName:@"header"]; +} + +/** The Device ID is a randomly generated integer between 1 and 2^31 - 1. If zero it means the element was not found. Only works within element.
*/ +- (uint32_t) omemo_senderDeviceId { + return [[self omemo_headerElement] attributeUInt32ValueForName:@"sid"]; +} + +/** key data is keyed to receiver deviceIds. Only works within element. BASE64ENCODED... .. */ +- (nullable NSArray*) omemo_keyData { + NSArray *keys = [[self omemo_headerElement] elementsForName:@"key"]; + if (!keys) { return nil; } + NSMutableArray *keyDataArray = [[NSMutableArray alloc] initWithCapacity:keys.count]; + [keys enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + uint32_t rid = [obj attributeUInt32ValueForName:@"rid"]; + NSString *b64 = [obj stringValue]; + NSData *data = nil; + if (b64) { + data = [[NSData alloc] initWithBase64EncodedString:b64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; + } + BOOL isPreKey = [obj attributeBoolValueForName:@"prekey"]; + if (rid > 0 && data) { + OMEMOKeyData *keyData = [[OMEMOKeyData alloc] initWithDeviceId:rid data:data isPreKey:isPreKey]; + [keyDataArray addObject:keyData]; + } + }]; + return [keyDataArray copy]; +} +/** Only works within element. BASE64ENCODED */ +- (nullable NSData*) omemo_payload { + NSString *b64 = [[self elementForName:@"payload"] stringValue]; + if (!b64) { return nil; } + return [[NSData alloc] initWithBase64EncodedString:b64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; +} + +- (nullable NSData*) omemo_iv { + NSXMLElement *header = [self omemo_headerElement]; + NSString *iv = [[header elementForName:@"iv"] stringValue]; + if (!iv) { return nil; } + return [[NSData alloc] initWithBase64EncodedString:iv options:NSDataBase64DecodingIgnoreUnknownCharacters]; +} + + +/** + * The client may wish to transmit keying material to the contact. This first has to be generated. The client MUST generate a fresh, randomly generated key/IV pair. For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a KeyTransportElement, omitting the as follows: + + +
+ BASE64ENCODED... + BASE64ENCODED... + + BASE64ENCODED... +
+
+ */ + ++ (NSXMLElement*) omemo_keyTransportElementWithKeyData:(NSArray*)keyData + iv:(NSData*)iv + senderDeviceId:(uint32_t)senderDeviceId xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + NSXMLElement *keyTransportElement = [NSXMLElement elementWithName:@"encrypted" xmlns:[OMEMOModule xmlnsOMEMO:xmlNamespace]]; + NSXMLElement *headerElement = [NSXMLElement elementWithName:@"header"]; + [headerElement addAttributeWithName:@"sid" unsignedIntegerValue:senderDeviceId]; + + [keyData enumerateObjectsUsingBlock:^(OMEMOKeyData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSXMLElement *keyElement = [NSXMLElement elementWithName:@"key" stringValue:[obj.data base64EncodedStringWithOptions:0]]; + [keyElement addAttributeWithName:@"rid" unsignedIntegerValue:obj.deviceId]; + if (obj.isPreKey) { + [keyElement addAttributeWithName:@"prekey" boolValue:YES]; + } + [headerElement addChild:keyElement]; + }]; + NSXMLElement *ivElement = [NSXMLElement elementWithName:@"iv" stringValue:[iv base64EncodedStringWithOptions:0]]; + [headerElement addChild:ivElement]; + [keyTransportElement addChild:headerElement]; + return keyTransportElement; +} + +/* + + + + + + + + + + + + */ +- (nullable NSArray*)omemo_deviceListFromIqResponse:(OMEMOModuleNamespace)ns { + NSXMLElement *pubsub = [self elementForName:@"pubsub" xmlns:XMLNS_PUBSUB]; + NSXMLElement *items = [pubsub elementForName:@"items"]; + return [items omemo_deviceListFromItems:ns]; +} + +- (nullable NSArray*)omemo_deviceListFromItems:(OMEMOModuleNamespace)ns { + if ([[self attributeStringValueForName:@"node"] isEqualToString:[OMEMOModule xmlnsOMEMODeviceList:ns]]) { + NSXMLElement * devicesList = [[self elementForName:@"item"] elementForName:@"list" xmlns:[OMEMOModule xmlnsOMEMO:ns]]; + if (devicesList) { + NSArray *children = [devicesList children]; + NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:children.count]; + [children enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { + if ([node.name isEqualToString:@"device"]) { + NSNumber *number = [node attributeNumberUInt32ValueForName:@"id"]; + if (number){ + [result addObject:number]; + } + } + }]; + return result; + } + return @[]; + } + return nil; +} + +@end diff --git a/Extensions/OMEMO/OMEMOBundle.h b/Extensions/OMEMO/OMEMOBundle.h new file mode 100644 index 0000000000..5384becf67 --- /dev/null +++ b/Extensions/OMEMO/OMEMOBundle.h @@ -0,0 +1,38 @@ +// +// OMEMOBundle.h +// XMPPFramework +// +// Created by Chris Ballinger on 9/9/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import +#import "OMEMOPreKey.h" +#import "OMEMOSignedPreKey.h" + +NS_ASSUME_NONNULL_BEGIN +@interface OMEMOBundle : NSObject + +/** The Device ID is a randomly generated integer between 1 and 2^31 - 1 */ +@property (nonatomic, readonly) uint32_t deviceId; +/** public part of identity key */ +@property (nonatomic, copy, readonly) NSData *identityKey; +@property (nonatomic, strong, readonly) OMEMOSignedPreKey *signedPreKey; +@property (nonatomic, copy, readonly) NSArray *preKeys; + +- (instancetype) initWithDeviceId:(uint32_t)deviceId + identityKey:(NSData*)identityKey + signedPreKey:(OMEMOSignedPreKey*)signedPreKey + preKeys:(NSArray*)preKeys NS_DESIGNATED_INITIALIZER; + +/** Not available, use designated initializer */ +- (instancetype) init NS_UNAVAILABLE; + +- (BOOL) isEqualToBundle:(OMEMOBundle*)bundle; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/OMEMOBundle.m b/Extensions/OMEMO/OMEMOBundle.m new file mode 100644 index 0000000000..984ad1edcf --- /dev/null +++ b/Extensions/OMEMO/OMEMOBundle.m @@ -0,0 +1,49 @@ +// +// OMEMOBundle.m +// XMPPFramework +// +// Created by Chris Ballinger on 9/9/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "OMEMOBundle.h" + +@implementation OMEMOBundle + +- (instancetype) init { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype) initWithDeviceId:(uint32_t)deviceId + identityKey:(NSData*)identityKey + signedPreKey:(OMEMOSignedPreKey*)signedPreKey + preKeys:(NSArray*)preKeys { + if (self = [super init]) { + _deviceId = deviceId; + _identityKey = [identityKey copy]; + _signedPreKey = signedPreKey; + _preKeys = [preKeys copy]; + } + return self; +} + +- (BOOL) isEqual:(id)object { + if ([object isKindOfClass:[OMEMOBundle class]]) { + return [self isEqualToBundle:object]; + } + return NO; +} + +- (BOOL) isEqualToBundle:(OMEMOBundle*)bundle { + return self.deviceId == bundle.deviceId && + [self.identityKey isEqualToData:bundle.identityKey] && + [self.signedPreKey isEqualToSignedPreKey:bundle.signedPreKey] && + [self.preKeys isEqualToArray:bundle.preKeys]; +} + +@end diff --git a/Extensions/OMEMO/OMEMOKeyData.h b/Extensions/OMEMO/OMEMOKeyData.h new file mode 100644 index 0000000000..f16b9fc814 --- /dev/null +++ b/Extensions/OMEMO/OMEMOKeyData.h @@ -0,0 +1,37 @@ +// +// OMEMOKeyData.h +// XMPPFramework +// +// Created by Chris Ballinger on 10/10/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import +#import + +NS_ASSUME_NONNULL_BEGIN +@interface OMEMOKeyData : NSObject + +@property (nonatomic, readonly, copy) NSData *data; +@property (nonatomic, readonly) uint32_t deviceId; + +/** + * This propery might be `NO` when using the legacy Converastions + * namespace because the original draft did not distinguish between + * PreKeyMessages and OMEMOMessages. + */ +@property (nonatomic, readonly) BOOL isPreKey; + +- (instancetype) initWithDeviceId:(uint32_t)deviceId + data:(NSData*)data + isPreKey:(BOOL)isPreKey NS_DESIGNATED_INITIALIZER; + +/** Not available, use designated initializer */ +- (instancetype) init NS_UNAVAILABLE; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/OMEMOKeyData.m b/Extensions/OMEMO/OMEMOKeyData.m new file mode 100644 index 0000000000..bb13466159 --- /dev/null +++ b/Extensions/OMEMO/OMEMOKeyData.m @@ -0,0 +1,35 @@ +// +// OMEMOKeyData.m +// XMPPFramework +// +// Created by Chris Ballinger on 10/10/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "OMEMOKeyData.h" + +@implementation OMEMOKeyData + +- (instancetype) init { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype) initWithDeviceId:(uint32_t)deviceId + data:(NSData*)data + isPreKey:(BOOL)isPreKey { + NSParameterAssert(deviceId != 0); + NSParameterAssert(data.length > 0); + if (self = [super init]) { + _deviceId = deviceId; + _data = [data copy]; + _isPreKey = isPreKey; + } + return self; +} + +@end diff --git a/Extensions/OMEMO/OMEMOModule.h b/Extensions/OMEMO/OMEMOModule.h new file mode 100644 index 0000000000..18376a90c5 --- /dev/null +++ b/Extensions/OMEMO/OMEMOModule.h @@ -0,0 +1,264 @@ +// +// OMEMOModule.h +// XMPPFramework +// +// Created by Chris Ballinger on 4/21/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. +// +// To use this module, you must also hook up a library compatible with +// the Double Ratchet algorithm [1] and X3DH [2]. You can use the +// SignalProtocol-ObjC [3] library but beware that it is GPL so cannot +// be used in closed source apps. +// +// 1. https://en.wikipedia.org/wiki/Double_Ratchet_Algorithm +// 2. https://whispersystems.org/docs/specifications/x3dh/ +// 3. https://github.com/ChatSecure/SignalProtocol-ObjC +// + +#import +#import "XMPP.h" +#import "XMPPCapabilities.h" +#import "OMEMOBundle.h" +#import "OMEMOKeyData.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, OMEMOModuleNamespace) { + /** Uses "eu.siacs.conversations.axolotl" namespace and compatible with the latest Conversations and ChatSecure versions as of Feb 8, 2017. */ + OMEMOModuleNamespaceConversationsLegacy, + /** Uses "urn:xmpp:omemo:0" namespace. XEP is still experimental. Do not use in production yet as it may change! See https://xmpp.org/extensions/xep-0384.html */ + OMEMOModuleNamespaceOMEMO +}; + +@protocol OMEMOStorageDelegate; + +/** + * XEP-0384 OMEMO Encryption + * http://xmpp.org/extensions/xep-0384.html + * + * This specification defines a protocol for end-to-end encryption in one-on-one chats that may have multiple clients per account. + */ +@interface OMEMOModule : XMPPModule + +@property (nonatomic, readonly) OMEMOModuleNamespace xmlNamespace; +@property (nonatomic, strong, readonly) id omemoStorage; + +#pragma mark Init + +- (instancetype) initWithOMEMOStorage:(id)omemoStorage xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; +- (instancetype) initWithOMEMOStorage:(id)omemoStorage xmlNamespace:(OMEMOModuleNamespace)xmlNamespace dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; + +/** Not available, use designated initializer */ +- (instancetype) init NS_UNAVAILABLE; +/** Not available, use designated initializer */ +- (instancetype) initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; + +#pragma mark Public methods + +/** + * In order for other devices to be able to initiate a session with a given device, it first has to announce itself by adding its device ID to the devicelist PEP node. + * + * Devices MUST check that their own device ID is contained in the list whenever they receive a PEP update from their own account. If they have been removed, they MUST reannounce themselves. + * + * @param deviceIds The Device ID is a randomly generated integer between 1 and 2^31 - 1 wrapped in an NSNumber. + * @param elementId XMPP element id. If nil a random UUID will be used. + */ +- (void) publishDeviceIds:(NSArray*)deviceIds + elementId:(nullable NSString*)elementId; + +/** For manually fetching deviceIds list. This should be handled automatically by PEP if you send a update on login. */ +- (void) fetchDeviceIdsForJID:(XMPPJID*)jid + elementId:(nullable NSString*)elementId; + +/** + * A device MUST announce it's IdentityKey, a signed PreKey, and a list of PreKeys in a separate, per-device PEP node. The list SHOULD contain 100 PreKeys, but MUST contain no less than 20. + * + * @param bundle your device bundle + * @param elementId XMPP element id. If nil a random UUID will be used. + */ +- (void) publishBundle:(OMEMOBundle*)bundle + elementId:(nullable NSString*)elementId; + +/** + * Fetches device bundle for a remote JID. + * + * @param deviceId remote deviceId + * @param jid remote JID + * @param elementId XMPP element id. If nil a random UUID will be used. + */ +- (void) fetchBundleForDeviceId:(uint32_t)deviceId + jid:(XMPPJID*)jid + elementId:(nullable NSString*)elementId; + +/** + * Remove a device from the remote XMPP server. Removes the bundle for each device. Fetches and removees the devices from the device list. + * The callbacks for removing a bundle (either success or failure) do not prevent the device list from being updated. + * + * Callbacks include: + * omemo:failedToRemoveDeviceIds:errorIq:elementId: + * omemo:removedBundleId:responseIq:outgoingIq:elementId: + * omemo:failedToRemoveBundleId:errorIq:outgoingIq:elementId: + * omemo:deviceListUpdate:fromJID:incomingElement: + * + * @param deviceIds remote deviceids + * @param elementId XMPP elementid. If nil a random UUID will be used. + */ +- (void) removeDeviceIds:(NSArray*)deviceIds + elementId:(nullable NSString *)elementId; + +/** + In order to send a chat message, its first has to be encrypted. The client MUST use fresh, randomly generated key/IV pairs with AES-128 in Galois/Counter Mode (GCM). For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a MessageElement. + + The client may wish to transmit keying material to the contact. This first has to be generated. The client MUST generate a fresh, randomly generated key/IV pair. For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a KeyTransportElement, omitting the as follows: + * + * @param payload data encrypted with fresh AES-128 GCM key/iv pair. If nil this is equivalent to a KeyTransportElement. + * @param jid recipient JID + * @param keyData payload's AES key encrypted to each recipient deviceId's Axolotl session + * @param iv the IV used for encryption of payload + * @param elementId XMPP element id. If nil a random UUID will be used. + */ +- (void) sendKeyData:(NSArray*)keyData + iv:(NSData*)iv + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + elementId:(nullable NSString*)elementId; + +/** Returns an unsent OMEMO message to be modified or sent elsewhere. Beware this may block! */ +- (nullable XMPPMessage*) messageForKeyData:(NSArray*)keyData + iv:(NSData*)iv + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + elementId:(nullable NSString*)elementId; + +#pragma mark Namespace methods + ++ (NSString*) xmlnsOMEMO:(OMEMOModuleNamespace)ns; ++ (NSString*) xmlnsOMEMODeviceList:(OMEMOModuleNamespace)ns; ++ (NSString*) xmlnsOMEMODeviceListNotify:(OMEMOModuleNamespace)ns; ++ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns; ++ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns deviceId:(uint32_t)deviceId; + +@end + +@protocol OMEMOModuleDelegate +@optional + +/** Callback for when your device list is successfully published */ +- (void)omemo:(OMEMOModule*)omemo +publishedDeviceIds:(NSArray*)deviceIds + responseIq:(XMPPIQ*)responseIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** Callback for when your device list update fails. If errorIq is nil there was a timeout. */ +- (void)omemo:(OMEMOModule*)omemo +failedToPublishDeviceIds:(NSArray*)deviceIds + errorIq:(nullable XMPPIQ*)errorIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** + * Device removal failed. The element Id is not the true element id of the sent stanza. It's used to track against the elmeent Id passed in the remove device method. + */ +- (void)omemo:(OMEMOModule*)omemo +failedToRemoveDeviceIds:(NSArray*)deviceIds + errorIq:(nullable XMPPIQ*)errorIq + elementId:(nullable NSString *)elementId; + + +/** + * In order to determine whether a given contact has devices that support OMEMO, the devicelist node in PEP is consulted. Devices MUST subscribe to 'urn:xmpp:omemo:0:devicelist' via PEP, so that they are informed whenever their contacts add a new device. They MUST cache the most up-to-date version of the devicelist. + */ +- (void)omemo:(OMEMOModule*)omemo deviceListUpdate:(NSArray*)deviceIds fromJID:(XMPPJID*)fromJID incomingElement:(XMPPElement*)incomingElement; + +/** Failed to fetch deviceList */ +- (void)omemo:(OMEMOModule*)omemo failedToFetchDeviceIdsForJID:(XMPPJID*)fromJID errorIq:(nullable XMPPIQ*)errorIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** Callback for when your bundle is successfully published */ +- (void)omemo:(OMEMOModule*)omemo + publishedBundle:(OMEMOBundle*)bundle + responseIq:(XMPPIQ*)responseIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** Callback when publishing your bundle fails */ +- (void)omemo:(OMEMOModule*)omemo +failedToPublishBundle:(OMEMOBundle*)bundle + errorIq:(nullable XMPPIQ*)errorIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** + * Process the incoming OMEMO bundle somewhere in your application + */ +- (void)omemo:(OMEMOModule*)omemo +fetchedBundle:(OMEMOBundle*)bundle + fromJID:(XMPPJID*)fromJID + responseIq:(XMPPIQ*)responseIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** Bundle fetch failed */ +- (void)omemo:(OMEMOModule*)omemo +failedToFetchBundleForDeviceId:(uint32_t)deviceId + fromJID:(XMPPJID*)fromJID + errorIq:(nullable XMPPIQ*)errorIq + outgoingIq:(XMPPIQ*)outgoingIq; + +/** + * Incoming MessageElement payload, keyData, and IV. If no payload it's a KeyTransportElement +- (void)omemo:(OMEMOModule*)omemo +failedToSendKeyData:(NSArray*)keyData + iv:(NSData*)iv + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + errorMessage:(nullable XMPPMessage*)errorMessage + outgoingMessage:(XMPPMessage*)outgoingMessage; + */ + +/** + * Incoming MessageElement payload, keyData, and IV. If no payload it's a KeyTransportElement */ +- (void)omemo:(OMEMOModule*)omemo +receivedKeyData:(NSArray*)keyData + iv:(NSData*)iv +senderDeviceId:(uint32_t)senderDeviceId + fromJID:(XMPPJID*)fromJID + payload:(nullable NSData*)payload + message:(XMPPMessage*)message; + + +/** This is called when receiving a MAM or Carbons message */ +- (void)omemo:(OMEMOModule*)omemo +receivedForwardedKeyData:(NSArray*)keyData + iv:(NSData*)iv +senderDeviceId:(uint32_t)senderDeviceId + forJID:(XMPPJID*)forJID + payload:(nullable NSData*)payload + isIncoming:(BOOL)isIncoming + delayed:(nullable NSDate*)delayed +forwardedMessage:(XMPPMessage*)forwardedMessage +originalMessage:(XMPPMessage*)originalMessage; + +@end + +@protocol OMEMOStorageDelegate +@required + +/** Return YES if successful. Optionally store a reference to parent module and moduleQueue */ +- (BOOL)configureWithParent:(OMEMOModule *)aParent queue:(dispatch_queue_t)queue; + +/** Store new deviceIds for a bare JID */ +- (void)storeDeviceIds:(NSArray*)deviceIds forJID:(XMPPJID*)jid; + +/** Fetch all deviceIds for a given bare JID. Return empty array if not found. */ +- (NSArray*)fetchDeviceIdsForJID:(XMPPJID*)jid; + +/** This should return your fully populated bundle with >= 100 prekeys. Return nil if bundle is not found. */ +- (nullable OMEMOBundle*)fetchMyBundle; + +/** Return YES if SignalProtocol session has been established and is valid */ +- (BOOL) isSessionValid:(XMPPJID*)jid deviceId:(uint32_t)deviceId; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/OMEMOModule.m b/Extensions/OMEMO/OMEMOModule.m new file mode 100644 index 0000000000..6bab077676 --- /dev/null +++ b/Extensions/OMEMO/OMEMOModule.m @@ -0,0 +1,441 @@ +// +// OMEMOModule.m +// XMPPFramework +// +// Created by Chris Ballinger on 4/21/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "OMEMOModule.h" +#import "XMPPPubSub.h" +#import "XMPPIQ+XEP_0060.h" +#import "XMPPIQ+OMEMO.h" +#import "XMPPMessage+OMEMO.h" +#import "XMPPIDTracker.h" +#import "XMPPLogging.h" +#import "XMPPMessage+XEP_0280.h" +#import "XMPPMessage+XEP_0313.h" +#import "NSXMLElement+XEP_0297.h" +#import "XMPPMessageCarbons.h" +#import "XMPPMessageArchiveManagement.h" + +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; + +@interface OMEMOModule() +@property (nonatomic, strong, readonly) XMPPIDTracker *tracker; +@end + +@implementation OMEMOModule + +#pragma mark Init + +- (instancetype) init { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype) initWithOMEMOStorage:(id)omemoStorage xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + return [self initWithOMEMOStorage:omemoStorage xmlNamespace:xmlNamespace dispatchQueue:self.moduleQueue]; +} + +- (instancetype) initWithDispatchQueue:(dispatch_queue_t)queue { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype) initWithOMEMOStorage:(id)omemoStorage xmlNamespace:(OMEMOModuleNamespace)xmlNamespace dispatchQueue:(nullable dispatch_queue_t)queue { + if (self = [super initWithDispatchQueue:queue]) { + if ([omemoStorage configureWithParent:self queue:moduleQueue]) { + _omemoStorage = omemoStorage; + } + _xmlNamespace = xmlNamespace; + } + return self; +} + +- (BOOL)activate:(XMPPStream *)aXmppStream +{ + if ([super activate:aXmppStream]) + { + [self performBlock:^{ + [self->xmppStream autoAddDelegate:self delegateQueue:self->moduleQueue toModulesOfClass:[XMPPCapabilities class]]; + self->_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:self->moduleQueue]; + }]; + return YES; + } + + return NO; +} + +- (void) deactivate { + [self performBlock:^{ + [self->_tracker removeAllIDs]; + self->_tracker = nil; + [self->xmppStream removeAutoDelegate:self delegateQueue:self->moduleQueue fromModulesOfClass:[XMPPCapabilities class]]; + }]; + [super deactivate]; +} + +#pragma mark Public methods + + +- (void) publishDeviceIds:(NSArray*)deviceIds elementId:(nullable NSString*)elementId { + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self performBlock:^{ + NSString *eid = [self fixElementId:elementId]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishDeviceIds:deviceIds elementId:eid xmlNamespace:self.xmlNamespace]; + [self.tracker addElement:iq block:^(XMPPIQ *responseIq, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + if (!responseIq || [responseIq isErrorIQ]) { + // timeout + XMPPLogWarn(@"publishDeviceIds error: %@ %@", iq, responseIq); + [weakMulticast omemo:strongSelf failedToPublishDeviceIds:deviceIds errorIq:responseIq outgoingIq:iq]; + return; + } + [weakMulticast omemo:strongSelf publishedDeviceIds:deviceIds responseIq:responseIq outgoingIq:iq]; + } timeout:30]; + [self->xmppStream sendElement:iq]; + }]; +} + +/** For fetching. This should be handled automatically by PEP. */ +- (void) fetchDeviceIdsForJID:(XMPPJID*)jid + elementId:(nullable NSString*)elementId { + NSParameterAssert(jid != nil); + if (!jid) { return; } + __block BOOL isOurJID = [self.xmppStream.myJID isEqualToJID:jid options:XMPPJIDCompareBare]; + [self fetchDeviceIdsForJID:jid elementId:elementId completion:^(XMPPIQ *responseIq, id info) { + // If we get an error response and this is our jid then we should process as if it's an empty device list. + if ((!responseIq || [responseIq isErrorIQ]) && !isOurJID) { + // timeout + XMPPLogWarn(@"fetchDeviceIdsForJID error: %@ %@", info.element, responseIq); + [self->multicastDelegate omemo:self failedToFetchDeviceIdsForJID:jid errorIq:responseIq outgoingIq:(XMPPIQ*)info.element]; + return; + } + + NSArray *devices = [responseIq omemo_deviceListFromIqResponse:self.xmlNamespace]; + if (!devices) { + devices = @[]; + XMPPLogWarn(@"Missing devices from element: %@ %@", info.element, responseIq); + } + + XMPPJID *bareJID = [[responseIq from] bareJID]; + if (bareJID == nil) { + // Normal iq responses to have a from attribute. Getting the from attrirbute from the outgoing to attribute. + // Should always be the account bare jid. + bareJID = [[[info element] to] bareJID]; + } + [self->multicastDelegate omemo:self deviceListUpdate:devices fromJID:bareJID incomingElement:responseIq]; + [self processIncomingDeviceIds:devices fromJID:bareJID]; + }]; +} + +- (void) fetchDeviceIdsForJID:(nonnull XMPPJID*)jid + elementId:(nullable NSString*)elementId + completion:(void (^_Nonnull)(XMPPIQ *responseIq, id info))completion { + [self performBlock:^{ + NSString *eid = [self fixElementId:elementId]; + XMPPIQ *iq = [XMPPIQ omemo_iqFetchDeviceIdsForJID:jid elementId:eid xmlNamespace:self.xmlNamespace]; + [self.tracker addElement:iq block:completion timeout:30]; + [self->xmppStream sendElement:iq]; + }]; +} + +- (void) publishBundle:(OMEMOBundle*)bundle + elementId:(nullable NSString*)elementId { + NSParameterAssert(bundle); + if (!bundle) { return; } + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self performBlock:^{ + NSString *eid = [self fixElementId:elementId]; + XMPPIQ *iq = [XMPPIQ omemo_iqPublishBundle:bundle elementId:eid xmlNamespace:self.xmlNamespace]; + [self.tracker addElement:iq block:^(XMPPIQ *responseIq, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + if (!responseIq || [responseIq isErrorIQ]) { + // timeout + XMPPLogWarn(@"publishBundle error: %@ %@", iq, responseIq); + [weakMulticast omemo:strongSelf failedToPublishBundle:bundle errorIq:responseIq outgoingIq:iq]; + return; + } + [weakMulticast omemo:strongSelf publishedBundle:bundle responseIq:responseIq outgoingIq:iq]; + } timeout:30]; + [self->xmppStream sendElement:iq]; + }]; +} + + +- (void) fetchBundleForDeviceId:(uint32_t)deviceId + jid:(XMPPJID*)jid + elementId:(nullable NSString*)elementId { + NSParameterAssert(jid); + if (!jid) { return; } + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self performBlock:^{ + NSString *eid = [self fixElementId:elementId]; + XMPPIQ *iq = [XMPPIQ omemo_iqFetchBundleForDeviceId:deviceId jid:jid.bareJID elementId:eid xmlNamespace:self.xmlNamespace]; + [self.tracker addElement:iq block:^(XMPPIQ *responseIq, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + if (!responseIq || [responseIq isErrorIQ]) { + // timeout + XMPPLogWarn(@"fetchBundleForDeviceId error: %@ %@", iq, responseIq); + [weakMulticast omemo:strongSelf failedToFetchBundleForDeviceId:deviceId fromJID:jid errorIq:responseIq outgoingIq:iq]; + return; + } + OMEMOBundle *bundle = [responseIq omemo_bundle:strongSelf.xmlNamespace]; + if (bundle) { + [weakMulticast omemo:strongSelf fetchedBundle:bundle fromJID:jid responseIq:responseIq outgoingIq:iq]; + } else { + XMPPLogWarn(@"fetchBundleForDeviceId bundle parsing error: %@ %@", iq, responseIq); + [weakMulticast omemo:strongSelf failedToFetchBundleForDeviceId:deviceId fromJID:jid errorIq:responseIq outgoingIq:iq]; + } + } timeout:30]; + [self->xmppStream sendElement:iq]; + }]; +} + +//All the different delegates that can happend. And you can't remove your self unless change delegate callback on bundle +- (void) removeDeviceIds:(NSArray*)deviceIds elementId:(NSString *)elementId { + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self performBlock:^{ + [self fetchDeviceIdsForJID:self.xmppStream.myJID elementId:nil completion:^(XMPPIQ *responseIq, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + if (!responseIq || [responseIq isErrorIQ]) { + // timeout + XMPPLogWarn(@"fetchDeviceIdsForJID error: %@ %@", info.element, responseIq); + [weakMulticast omemo:strongSelf failedToRemoveDeviceIds:deviceIds errorIq:responseIq elementId:elementId]; + return; + } + + NSArray *devices = [responseIq omemo_deviceListFromIqResponse:strongSelf.xmlNamespace]; + NSIndexSet *indexSet = [devices indexesOfObjectsPassingTest:^BOOL(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + return [deviceIds containsObject:obj]; + }]; + NSMutableArray *mutableDevices = [devices mutableCopy]; + // Remove devices + [mutableDevices removeObjectsAtIndexes:indexSet]; + //publish new list of devices + [strongSelf publishDeviceIds:[mutableDevices copy] elementId:elementId]; + }]; + }]; +} + +- (void) sendKeyData:(NSArray*)keyData + iv:(NSData*)iv + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + elementId:(nullable NSString*)elementId { + XMPPMessage *message = [self messageForKeyData:keyData iv:iv toJID:toJID payload:payload elementId:elementId]; + if (message) { + [xmppStream sendElement:message]; + } +} + +- (nullable XMPPMessage*) messageForKeyData:(NSArray*)keyData + iv:(NSData*)iv + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + elementId:(nullable NSString*)elementId { + NSParameterAssert(keyData.count > 0); + NSParameterAssert(iv.length > 0); + NSParameterAssert(toJID != nil); + if (!keyData.count || !iv.length || !toJID) { + return nil; + } + __block XMPPMessage *message = nil; + [self performBlock:^{ + OMEMOBundle *myBundle = [self.omemoStorage fetchMyBundle]; + if (!myBundle) { + XMPPLogWarn(@"sendKeyData: could not fetch my bundle"); + return; + } + NSString *eid = [self fixElementId:elementId]; + message = [XMPPMessage omemo_messageWithKeyData:keyData iv:iv senderDeviceId:myBundle.deviceId toJID:toJID payload:payload elementId:eid xmlNamespace:self.xmlNamespace]; + }]; + return message; +} + +#pragma mark Private Methods + +/** If message was extracted from carbons or MAM, originalMessage will reflect the contents of the originally received message stanza */ +- (void) receiveMessage:(XMPPMessage*)message forJID:(XMPPJID*)forJID isIncoming:(BOOL)isIncoming delayed:(nullable NSDate*)delayed originalMessage:(XMPPMessage*)originalMessage { + NSParameterAssert(message); + NSParameterAssert(forJID); + NSParameterAssert(originalMessage); + if (!message || !forJID || !originalMessage) { + return; + } + // Check for incoming device list updates + NSArray *deviceIds = [message omemo_deviceListFromPEPUpdate:self.xmlNamespace]; + XMPPJID *bareJID = forJID.bareJID; + if (deviceIds && message == originalMessage) { + [multicastDelegate omemo:self deviceListUpdate:deviceIds fromJID:bareJID incomingElement:message]; + [self processIncomingDeviceIds:deviceIds fromJID:bareJID]; + return; + } + NSXMLElement *omemo = [message omemo_encryptedElement:self.xmlNamespace]; + if (!omemo) { return; } + uint32_t deviceId = [omemo omemo_senderDeviceId]; + NSArray* keyData = [omemo omemo_keyData]; + NSData *iv = [omemo omemo_iv]; + NSData *payload = [omemo omemo_payload]; + if (deviceId > 0 && keyData.count > 0 && iv) { + if (message == originalMessage) { + [multicastDelegate omemo:self receivedKeyData:keyData iv:iv senderDeviceId:deviceId fromJID:bareJID payload:payload message:originalMessage]; + } else { + [multicastDelegate omemo:self receivedForwardedKeyData:keyData iv:iv senderDeviceId:deviceId forJID:bareJID payload:payload isIncoming:isIncoming delayed:delayed forwardedMessage:message originalMessage:originalMessage]; + } + } +} + +#pragma mark Namespace methods + ++ (NSString*) xmlnsOMEMO:(OMEMOModuleNamespace)ns { + if (ns == OMEMOModuleNamespaceOMEMO) { + return @"urn:xmpp:omemo:0"; + } else { // OMEMOModuleNamespaceConversationsLegacy + return @"eu.siacs.conversations.axolotl"; + } +} ++ (NSString*) xmlnsOMEMODeviceList:(OMEMOModuleNamespace)ns { + NSString *xmlns = [self xmlnsOMEMO:ns]; + if (ns == OMEMOModuleNamespaceOMEMO) { + return [NSString stringWithFormat:@"%@:devicelist", xmlns]; + } else { // OMEMOModuleNamespaceConversationsLegacy + return [NSString stringWithFormat:@"%@.devicelist", xmlns]; + } +} ++ (NSString*) xmlnsOMEMODeviceListNotify:(OMEMOModuleNamespace)ns { + return [NSString stringWithFormat:@"%@+notify", [self xmlnsOMEMODeviceList:ns]]; +} ++ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns { + NSString *xmlns = [self xmlnsOMEMO:ns]; + if (ns == OMEMOModuleNamespaceOMEMO) { + xmlns = [NSString stringWithFormat:@"%@:bundles", xmlns]; + } else { // OMEMOModuleNamespaceConversationsLegacy + xmlns = [NSString stringWithFormat:@"%@.bundles", xmlns]; + } + NSParameterAssert(xmlns != nil); + return xmlns; +} + ++ (NSString*) xmlnsOMEMOBundles:(OMEMOModuleNamespace)ns deviceId:(uint32_t)deviceId { + return [NSString stringWithFormat:@"%@:%d", [self xmlnsOMEMOBundles:ns], (int)deviceId]; +} + +#pragma mark XMPPStreamDelegate methods + +- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender { + OMEMOBundle *myBundle = [self.omemoStorage fetchMyBundle]; + [self fetchDeviceIdsForJID:sender.myJID elementId:nil]; + if (myBundle) { + [self publishBundle:myBundle elementId:nil]; + } +} + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message { + XMPPJID *myJID = sender.myJID; + if (!myJID) { return; } + NSXMLElement *mamResult = message.mamResult; + if ([message isTrustedMessageCarbonForMyJID:myJID]) { + XMPPMessage *carbon = message.messageCarbonForwardedMessage; + BOOL isIncoming = !message.isSentMessageCarbon; + XMPPJID *forJID = nil; + if (isIncoming) { + forJID = carbon.from; + } else { + forJID = carbon.to; + } + if (!forJID) { + return; + } + [self receiveMessage:carbon forJID:forJID isIncoming:isIncoming delayed:nil originalMessage:message]; + } else if (mamResult) { + XMPPMessage *mam = mamResult.forwardedMessage; + BOOL isIncoming = [mam.to isEqualToJID:myJID options:XMPPJIDCompareBare]; + XMPPJID *forJID = nil; + if (isIncoming) { + forJID = mam.from; + } else { + forJID = mam.to; + } + if (!forJID) { + return; + } + NSDate *delayed = mamResult.forwardedStanzaDelayedDeliveryDate; + [self receiveMessage:mam forJID:forJID isIncoming:isIncoming delayed:delayed originalMessage:message]; + } else { + [self receiveMessage:message forJID:message.from isIncoming:YES delayed:nil originalMessage:message]; + } +} + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { + BOOL success = NO; + if (!iq.from) { + // Some error responses for self or contacts don't have a "from" + success = [self.tracker invokeForID:iq.elementID withObject:iq]; + } else { + success = [self.tracker invokeForElement:iq withObject:iq]; + } + //DDLogWarn(@"Could not match IQ: %@", iq); + return success; +} + +#pragma mark XMPPCapabilitiesDelegate methods + +- (NSArray*) myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender { + return @[[[self class] xmlnsOMEMODeviceList:self.xmlNamespace], [[self class] xmlnsOMEMODeviceListNotify:self.xmlNamespace]]; +} + +#pragma mark Utility + +/** Generate elementId UUID if needed */ +- (nonnull NSString*) fixElementId:(nullable NSString*)elementId { + NSString *eid = nil; + if (!elementId.length) { + eid = [[NSUUID UUID] UUIDString]; + } else { + eid = [elementId copy]; + } + return eid; +} + +- (void) processIncomingDeviceIds:(NSArray*)deviceIds fromJID:(XMPPJID*)fromJID { + NSParameterAssert(fromJID != nil); + NSParameterAssert(deviceIds != nil); + if (!fromJID || !deviceIds) { + return; + } + fromJID = [fromJID bareJID]; + // This may temporarily remove your own deviceId until we can update (below) + [self.omemoStorage storeDeviceIds:deviceIds forJID:fromJID]; + + // Check if your device is contained in the update + if ([fromJID isEqualToJID:xmppStream.myJID options:XMPPJIDCompareBare]) { + OMEMOBundle *myBundle = [self.omemoStorage fetchMyBundle]; + if (!myBundle) { + return; + } + if([deviceIds containsObject:@(myBundle.deviceId)]) { + return; + } + // Republish deviceIds with your deviceId + NSArray *appended = [deviceIds arrayByAddingObject:@(myBundle.deviceId)]; + [self.omemoStorage storeDeviceIds:appended forJID:fromJID]; + [self publishDeviceIds:appended elementId:[[NSUUID UUID] UUIDString]]; + } + +} + +@end diff --git a/Extensions/OMEMO/OMEMOPreKey.h b/Extensions/OMEMO/OMEMOPreKey.h new file mode 100644 index 0000000000..82dd9cbe26 --- /dev/null +++ b/Extensions/OMEMO/OMEMOPreKey.h @@ -0,0 +1,30 @@ +// +// OMEMOPreKey.h +// Pods +// +// Created by Chris Ballinger on 9/20/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import + +NS_ASSUME_NONNULL_BEGIN +@interface OMEMOPreKey : NSObject + +@property (nonatomic, readonly) uint32_t preKeyId; +@property (nonatomic, copy, readonly) NSData *publicKey; + +- (instancetype) initWithPreKeyId:(uint32_t)preKeyId + publicKey:(NSData*)publicKey NS_DESIGNATED_INITIALIZER; + +/** Not available, use designated initializer */ +- (instancetype) init NS_UNAVAILABLE; + +- (BOOL) isEqualToPreKey:(OMEMOPreKey*)preKey; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/OMEMOPreKey.m b/Extensions/OMEMO/OMEMOPreKey.m new file mode 100644 index 0000000000..6d97506679 --- /dev/null +++ b/Extensions/OMEMO/OMEMOPreKey.m @@ -0,0 +1,43 @@ +// +// OMEMOPreKey.m +// Pods +// +// Created by Chris Ballinger on 9/20/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "OMEMOPreKey.h" + +@implementation OMEMOPreKey + +- (instancetype) init { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype) initWithPreKeyId:(uint32_t)preKeyId + publicKey:(NSData*)publicKey { + if (self = [super init]) { + _preKeyId = preKeyId; + _publicKey = [publicKey copy]; + } + return self; +} + +- (BOOL) isEqual:(id)object { + if ([object isKindOfClass:[OMEMOPreKey class]]) { + return [self isEqualToPreKey:object]; + } + return NO; +} + +- (BOOL) isEqualToPreKey:(OMEMOPreKey*)preKey { + return self.preKeyId == preKey.preKeyId && + [self.publicKey isEqualToData:preKey.publicKey]; +} + +@end diff --git a/Extensions/OMEMO/OMEMOSignedPreKey.h b/Extensions/OMEMO/OMEMOSignedPreKey.h new file mode 100644 index 0000000000..35a39533bd --- /dev/null +++ b/Extensions/OMEMO/OMEMOSignedPreKey.h @@ -0,0 +1,31 @@ +// +// OMEMOSignedPreKey.h +// Pods +// +// Created by Chris Ballinger on 9/20/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "OMEMOPreKey.h" + +NS_ASSUME_NONNULL_BEGIN +@interface OMEMOSignedPreKey : OMEMOPreKey + +@property (nonatomic, copy, readonly) NSData *signature; + +- (instancetype) initWithPreKeyId:(uint32_t)preKeyId + publicKey:(NSData*)publicKey + signature:(NSData*)signature NS_DESIGNATED_INITIALIZER; + +/** Not available, use designated initializer */ +- (instancetype) initWithPreKeyId:(uint32_t)preKeyId + publicKey:(NSData*)publicKey NS_UNAVAILABLE; + +- (BOOL) isEqualToSignedPreKey:(OMEMOSignedPreKey*)signedPreKey; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/OMEMOSignedPreKey.m b/Extensions/OMEMO/OMEMOSignedPreKey.m new file mode 100644 index 0000000000..dfb76dfc91 --- /dev/null +++ b/Extensions/OMEMO/OMEMOSignedPreKey.m @@ -0,0 +1,44 @@ +// +// OMEMOSignedPreKey.m +// Pods +// +// Created by Chris Ballinger on 9/20/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "OMEMOSignedPreKey.h" + +@implementation OMEMOSignedPreKey + +- (instancetype) initWithPreKeyId:(uint32_t)preKeyId + publicKey:(NSData*)publicKey { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype) initWithPreKeyId:(uint32_t)preKeyId + publicKey:(NSData*)publicKey + signature:(NSData*)signature { + if (self = [super initWithPreKeyId:preKeyId publicKey:publicKey]) { + _signature = [signature copy]; + } + return self; +} + +- (BOOL) isEqual:(id)object { + if ([object isKindOfClass:[OMEMOSignedPreKey class]]) { + return [self isEqualToSignedPreKey:object]; + } + return NO; +} + +- (BOOL) isEqualToSignedPreKey:(OMEMOSignedPreKey*)signedPreKey { + return [super isEqualToPreKey:signedPreKey] && + [self.signature isEqualToData:signedPreKey.signature]; +} + +@end diff --git a/Extensions/OMEMO/XMPPIQ+OMEMO.h b/Extensions/OMEMO/XMPPIQ+OMEMO.h new file mode 100644 index 0000000000..87d41dd8bd --- /dev/null +++ b/Extensions/OMEMO/XMPPIQ+OMEMO.h @@ -0,0 +1,45 @@ +// +// XMPPIQ+OMEMO.h +// XMPPFramework +// +// Created by Chris Ballinger on 4/21/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import +#import "XMPPIQ.h" +#import "OMEMOModule.h" + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPIQ (OMEMO) + +/** iq stanza for manually fetching deviceIds list. This should be handled automatically by PEP. */ ++ (XMPPIQ*) omemo_iqFetchDeviceIdsForJID:(XMPPJID*)jid + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + +/** iq stanza for publishing your device ids. The Device IDs are integers between 1 and 2^31 - 1 */ ++ (XMPPIQ*) omemo_iqPublishDeviceIds:(NSArray*)deviceIds + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + +/** iq stanza for publishing bundle for device */ ++ (XMPPIQ*) omemo_iqPublishBundle:(OMEMOBundle*)bundle + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + +/** iq stanza for fetching remote bundle */ ++ (XMPPIQ*) omemo_iqFetchBundleForDeviceId:(uint32_t)deviceId + jid:(XMPPJID*)jid + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + +/** Serialize bundle from IQ */ +- (nullable OMEMOBundle*) omemo_bundle:(OMEMOModuleNamespace)ns; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/OMEMO/XMPPIQ+OMEMO.m b/Extensions/OMEMO/XMPPIQ+OMEMO.m new file mode 100644 index 0000000000..2288bc3eb0 --- /dev/null +++ b/Extensions/OMEMO/XMPPIQ+OMEMO.m @@ -0,0 +1,349 @@ +// +// XMPPIQ+OMEMO.m +// XMPPFramework +// +// Created by Chris Ballinger on 4/21/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import "XMPPIQ+OMEMO.h" +#import "XMPPIQ+XEP_0060.h" +#import "OMEMOModule.h" + +@implementation XMPPIQ (OMEMO) + + +/** + + + + + + */ ++ (XMPPIQ*) omemo_iqFetchDeviceIdsForJID:(XMPPJID*)jid + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + NSXMLElement *items = [NSXMLElement elementWithName:@"items"]; + [items addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMODeviceList:xmlNamespace]]; + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [pubsub addChild:items]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:jid.bareJID elementID:elementId]; + [iq addChild:pubsub]; + return iq; +} + + +/** + + + + + + + + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + 1 + + + open + + + + + + */ ++ (XMPPIQ*) omemo_iqPublishDeviceIds:(NSArray*)deviceIds elementId:(nullable NSString*)elementId xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + NSXMLElement *listElement = [NSXMLElement elementWithName:@"list" xmlns:[OMEMOModule xmlnsOMEMO:xmlNamespace]]; + [deviceIds enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSXMLElement *device = [NSXMLElement elementWithName:@"device"]; + [device addAttributeWithName:@"id" numberValue:obj]; + [listElement addChild:device]; + }]; + + NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; + [item addChild:listElement]; + + NSXMLElement *publish = [NSXMLElement elementWithName:@"publish"]; + [publish addAttributeWithName:@"node" stringValue:[OMEMOModule xmlnsOMEMODeviceList:xmlNamespace]]; + [publish addChild:item]; + + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [pubsub addChild:publish]; + + NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; + [x addAttributeWithName:@"type" stringValue:@"submit"]; + + NSXMLElement *formTypeField = [NSXMLElement elementWithName:@"field"]; + [formTypeField addAttributeWithName:@"var" stringValue:@"FORM_TYPE"]; + [formTypeField addAttributeWithName:@"type" stringValue:@"hidden"]; + [formTypeField addChild:[NSXMLElement elementWithName:@"value" stringValue:XMLNS_PUBSUB_PUBLISH_OPTIONS]]; + + [x addChild:formTypeField]; + + NSXMLElement *persistanceField = [NSXMLElement elementWithName:@"field"]; + [persistanceField addAttributeWithName:@"var" stringValue:@"pubsub#persist_items"]; + [persistanceField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"1"]]; + + [x addChild:persistanceField]; + + NSXMLElement *accessModelField = [NSXMLElement elementWithName:@"field"]; + [accessModelField addAttributeWithName:@"var" stringValue:@"pubsub#access_model"]; + [accessModelField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"open"]]; + + [x addChild:accessModelField]; + + NSXMLElement *publishOptions = [NSXMLElement elementWithName:@"publish-options"]; + [publishOptions addChild:x]; + + [pubsub addChild:publishOptions]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; + [iq addChild:pubsub]; + + return iq; +} + +/** iq stanza for publishing bundle for device + + + + + + + + BASE64ENCODED... + + + BASE64ENCODED... + + + BASE64ENCODED... + + + + BASE64ENCODED... + + + BASE64ENCODED... + + + BASE64ENCODED... + + + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + 1 + + + open + + + + + + + */ ++ (XMPPIQ*) omemo_iqPublishBundle:(OMEMOBundle*)bundle + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + NSXMLElement *signedPreKeyElement = nil; + if (bundle.signedPreKey.publicKey) { + signedPreKeyElement = [NSXMLElement elementWithName:@"signedPreKeyPublic" stringValue:[bundle.signedPreKey.publicKey base64EncodedStringWithOptions:0]]; + [signedPreKeyElement addAttributeWithName:@"signedPreKeyId" unsignedIntegerValue:bundle.signedPreKey.preKeyId]; + } + NSXMLElement *signedPreKeySignatureElement = nil; + if (bundle.signedPreKey.signature) { + signedPreKeySignatureElement = [NSXMLElement elementWithName:@"signedPreKeySignature" stringValue:[bundle.signedPreKey.signature base64EncodedStringWithOptions:0]]; + } + NSXMLElement *identityKeyElement = nil; + if (bundle.identityKey) { + identityKeyElement = [NSXMLElement elementWithName:@"identityKey" stringValue:[bundle.identityKey base64EncodedStringWithOptions:0]]; + } + NSXMLElement *preKeysElement = [NSXMLElement elementWithName:@"prekeys"]; + [bundle.preKeys enumerateObjectsUsingBlock:^(OMEMOPreKey * _Nonnull preKey, NSUInteger idx, BOOL * _Nonnull stop) { + NSXMLElement *preKeyElement = [NSXMLElement elementWithName:@"preKeyPublic" stringValue:[preKey.publicKey base64EncodedStringWithOptions:0]]; + [preKeyElement addAttributeWithName:@"preKeyId" unsignedIntegerValue:preKey.preKeyId]; + [preKeysElement addChild:preKeyElement]; + }]; + NSXMLElement *bundleElement = [XMPPElement elementWithName:@"bundle" xmlns:[OMEMOModule xmlnsOMEMO:xmlNamespace]]; + if (signedPreKeyElement) { + [bundleElement addChild:signedPreKeyElement]; + } + if (signedPreKeySignatureElement) { + [bundleElement addChild:signedPreKeySignatureElement]; + } + if (identityKeyElement) { + [bundleElement addChild:identityKeyElement]; + } + [bundleElement addChild:preKeysElement]; + NSXMLElement *itemElement = [NSXMLElement elementWithName:@"item"]; + [itemElement addChild:bundleElement]; + + NSXMLElement *publish = [NSXMLElement elementWithName:@"publish"]; + NSString *nodeName = [OMEMOModule xmlnsOMEMOBundles:xmlNamespace deviceId:bundle.deviceId]; + [publish addAttributeWithName:@"node" stringValue:nodeName]; + [publish addChild:itemElement]; + + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [pubsub addChild:publish]; + + NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; + [x addAttributeWithName:@"type" stringValue:@"submit"]; + + NSXMLElement *formTypeField = [NSXMLElement elementWithName:@"field"]; + [formTypeField addAttributeWithName:@"var" stringValue:@"FORM_TYPE"]; + [formTypeField addAttributeWithName:@"type" stringValue:@"hidden"]; + [formTypeField addChild:[NSXMLElement elementWithName:@"value" stringValue:XMLNS_PUBSUB_PUBLISH_OPTIONS]]; + + [x addChild:formTypeField]; + + NSXMLElement *persistanceField = [NSXMLElement elementWithName:@"field"]; + [persistanceField addAttributeWithName:@"var" stringValue:@"pubsub#persist_items"]; + [persistanceField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"1"]]; + + [x addChild:persistanceField]; + + NSXMLElement *accessModelField = [NSXMLElement elementWithName:@"field"]; + [accessModelField addAttributeWithName:@"var" stringValue:@"pubsub#access_model"]; + [accessModelField addChild:[NSXMLElement elementWithName:@"value" objectValue:@"open"]]; + + [x addChild:accessModelField]; + + NSXMLElement *publishOptions = [NSXMLElement elementWithName:@"publish-options"]; + [publishOptions addChild:x]; + + [pubsub addChild:publishOptions]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; + [iq addChild:pubsub]; + return iq; +} + + ++ (XMPPIQ *) omemo_iqFetchNode:(NSString *)node to:(XMPPJID *)toJID elementId:(nullable NSString*)elementId { + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:toJID elementID:elementId]; + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + NSXMLElement *itemsElement = [NSXMLElement elementWithName:@"items"]; + [itemsElement addAttributeWithName:@"node" stringValue:node]; + + [pubsub addChild:itemsElement]; + [iq addChild:pubsub]; + + return iq; +} + ++ (XMPPIQ *) omemo_iqDeleteNode:(NSString *)node elementId:(nullable NSString *)elementId { + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementId]; + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + NSXMLElement *deleteElement = [NSXMLElement elementWithName:@"retract"]; + [deleteElement addAttributeWithName:@"node" stringValue:node]; + + [pubsub addChild:deleteElement]; + [iq addChild:pubsub]; + + return iq; +} +/** + * iq stanza for fetching remote bundle + + + + + + + + */ ++ (XMPPIQ*) omemo_iqFetchBundleForDeviceId:(uint32_t)deviceId + jid:(XMPPJID*)jid + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace { + NSString *nodeName = [OMEMOModule xmlnsOMEMOBundles:xmlNamespace deviceId:deviceId]; + return [self omemo_iqFetchNode:nodeName to:jid elementId:elementId]; +} + + +- (nullable OMEMOBundle*) omemo_bundle:(OMEMOModuleNamespace)ns { + NSXMLElement *pubsub = [self elementForName:@"pubsub" xmlns:XMLNS_PUBSUB]; + if (!pubsub) { return nil; } + NSXMLElement *items = [pubsub elementForName:@"items"]; + // If !items, this is a bundle and used for testing + if (!items) { + items = [pubsub elementForName:@"publish"]; + } + if (!items) { return nil; } + NSString *node = [items attributeForName:@"node"].stringValue; + if (!node) { return nil; } + if (![node containsString:[OMEMOModule xmlnsOMEMOBundles:ns]]) { + return nil; + } + NSString *separator = [[OMEMOModule xmlnsOMEMOBundles:ns] stringByAppendingString:@":"]; + NSArray *components = [node componentsSeparatedByString:separator]; + NSString *deviceIdString = [components lastObject]; + uint32_t deviceId = (uint32_t)[deviceIdString integerValue]; + + NSXMLElement *itemElement = [items elementForName:@"item"]; + if (!itemElement) { return nil; } + NSXMLElement *bundleElement = [itemElement elementForName:@"bundle" xmlns:[OMEMOModule xmlnsOMEMO:ns]]; + if (!bundleElement) { return nil; } + NSXMLElement *signedPreKeyElement = [bundleElement elementForName:@"signedPreKeyPublic"]; + if (!signedPreKeyElement) { return nil; } + uint32_t signedPreKeyId = [signedPreKeyElement attributeUInt32ValueForName:@"signedPreKeyId"]; + NSString *signedPreKeyPublicBase64 = [signedPreKeyElement stringValue]; + if (!signedPreKeyPublicBase64) { return nil; } + NSData *signedPreKeyPublic = [[NSData alloc] initWithBase64EncodedString:signedPreKeyPublicBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; + if (!signedPreKeyPublic) { return nil; } + NSString *signedPreKeySignatureBase64 = [[bundleElement elementForName:@"signedPreKeySignature"] stringValue]; + if (!signedPreKeySignatureBase64) { return nil; } + NSData *signedPreKeySignature = [[NSData alloc] initWithBase64EncodedString:signedPreKeySignatureBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; + if (!signedPreKeySignature) { return nil; } + NSString *identityKeyBase64 = [[bundleElement elementForName:@"identityKey"] stringValue]; + if (!identityKeyBase64) { return nil; } + NSData *identityKey = [[NSData alloc] initWithBase64EncodedString:identityKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; + if (!identityKey) { return nil; } + NSXMLElement *preKeysElement = [bundleElement elementForName:@"prekeys"]; + if (!preKeysElement) { return nil; } + NSArray *preKeyElements = [preKeysElement elementsForName:@"preKeyPublic"]; + NSMutableArray *preKeys = [NSMutableArray arrayWithCapacity:preKeyElements.count]; + [preKeyElements enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + uint32_t preKeyId = [obj attributeUInt32ValueForName:@"preKeyId"]; + NSString *b64 = [obj stringValue]; + NSData *data = nil; + if (b64) { + data = [[NSData alloc] initWithBase64EncodedString:b64 options:NSDataBase64DecodingIgnoreUnknownCharacters]; + } + if (data) { + OMEMOPreKey *preKey = [[OMEMOPreKey alloc] initWithPreKeyId:preKeyId publicKey:data]; + [preKeys addObject:preKey]; + } + }]; + OMEMOSignedPreKey *signedPreKey = [[OMEMOSignedPreKey alloc] initWithPreKeyId:signedPreKeyId publicKey:signedPreKeyPublic signature:signedPreKeySignature]; + OMEMOBundle *bundle = [[OMEMOBundle alloc] initWithDeviceId:deviceId identityKey:identityKey signedPreKey:signedPreKey preKeys:preKeys]; + return bundle; +} + +@end diff --git a/Extensions/OMEMO/XMPPMessage+OMEMO.h b/Extensions/OMEMO/XMPPMessage+OMEMO.h new file mode 100644 index 0000000000..c652ebd348 --- /dev/null +++ b/Extensions/OMEMO/XMPPMessage+OMEMO.h @@ -0,0 +1,53 @@ +// +// XMPPMessage+OMEMO.h +// XMPPFramework +// +// Created by Chris Ballinger on 4/21/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import +#import "XMPPMessage.h" +#import "NSXMLElement+OMEMO.h" + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPMessage (OMEMO) + +/** Extracts device list from PEP update */ +- (nullable NSArray*)omemo_deviceListFromPEPUpdate:(OMEMOModuleNamespace)ns; + +/** + In order to send a chat message, its first has to be encrypted. The client MUST use fresh, randomly generated key/IV pairs with AES-128 in Galois/Counter Mode (GCM). For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a MessageElement. + + + +
+ BASE64ENCODED... + BASE64ENCODED... + + BASE64ENCODED... +
+ BASE64ENCODED +
+ +
+ + If payload is nil, this message will contain a KeyTransportElement. + + keyData is keyed to the receiving deviceIds + */ ++ (XMPPMessage*) omemo_messageWithKeyData:(NSArray*)keyData + iv:(NSData*)iv + senderDeviceId:(uint32_t)senderDeviceId + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace; + +@end +NS_ASSUME_NONNULL_END + diff --git a/Extensions/OMEMO/XMPPMessage+OMEMO.m b/Extensions/OMEMO/XMPPMessage+OMEMO.m new file mode 100644 index 0000000000..45c0c53457 --- /dev/null +++ b/Extensions/OMEMO/XMPPMessage+OMEMO.m @@ -0,0 +1,67 @@ +// +// XMPPMessage+OMEMO.m +// XMPPFramework +// +// Created by Chris Ballinger on 4/21/16. +// +// XEP-0384: OMEMO Encryption +// https://xmpp.org/extensions/xep-0384.html +// +// This specification defines a protocol for end-to-end encryption +// in one-on-one chats that may have multiple clients per account. + +#import +#import "XMPPMessage+OMEMO.h" +#import "OMEMOModule.h" +#import "XMPPMessage+XEP_0334.h" +#import "NSXMLElement+XMPP.h" +#import "XMPPIQ+XEP_0060.h" + +@implementation XMPPMessage (OMEMO) + +- (nullable NSArray*)omemo_deviceListFromPEPUpdate:(OMEMOModuleNamespace)ns +{ + NSXMLElement *event = [self elementForName:@"event" xmlns:XMLNS_PUBSUB_EVENT]; + if (!event) { return nil; } + NSXMLElement * itemsList = [event elementForName:@"items"]; + if (!itemsList) { return nil; } + return [itemsList omemo_deviceListFromItems:ns]; +} + +/** + In order to send a chat message, its first has to be encrypted. The client MUST use fresh, randomly generated key/IV pairs with AES-128 in Galois/Counter Mode (GCM). For each intended recipient device, i.e. both own devices as well as devices associated with the contact, this key is encrypted using the corresponding long-standing axolotl session. Each encrypted payload key is tagged with the recipient device's ID. This is all serialized into a MessageElement, which is transmitted in a as follows: + + + +
+ BASE64ENCODED... + BASE64ENCODED... + + BASE64ENCODED... +
+ BASE64ENCODED +
+ +
+ */ + ++ (XMPPMessage*) omemo_messageWithKeyData:(NSArray*)keyData + iv:(NSData*)iv + senderDeviceId:(uint32_t)senderDeviceId + toJID:(XMPPJID*)toJID + payload:(nullable NSData*)payload + elementId:(nullable NSString*)elementId + xmlNamespace:(OMEMOModuleNamespace)xmlNamespace{ + NSXMLElement *encryptedElement = [NSXMLElement omemo_keyTransportElementWithKeyData:keyData iv:iv senderDeviceId:senderDeviceId xmlNamespace:xmlNamespace]; + if (payload) { + NSString *b64 = [payload base64EncodedStringWithOptions:0]; + NSXMLElement *payloadElement = [NSXMLElement elementWithName:@"payload" stringValue:b64]; + [encryptedElement addChild:payloadElement]; + } + XMPPMessage *messageElement = [XMPPMessage messageWithType:@"chat" to:toJID elementID:elementId]; + [messageElement addStorageHint:XMPPMessageStorageStore]; + [messageElement addChild:encryptedElement]; + return messageElement; +} + +@end diff --git a/Extensions/OneToOneChat/XMPPOneToOneChat.h b/Extensions/OneToOneChat/XMPPOneToOneChat.h new file mode 100644 index 0000000000..a635aefc4a --- /dev/null +++ b/Extensions/OneToOneChat/XMPPOneToOneChat.h @@ -0,0 +1,27 @@ +#import "XMPPModule.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessage; + +/// @brief A module that handles one-to-one chat messaging. +/// @discussion This module triggers delegate callbacks for all sent or received messages of type 'chat'. +@interface XMPPOneToOneChat : XMPPModule + +@end + +/// A protocol defining @c XMPPOneToOneChat module delegate API. +@protocol XMPPOneToOneChatDelegate + +@optional +/// Notifies the delegate that a chat message has been received in the stream. +- (void)xmppOneToOneChat:(XMPPOneToOneChat *)xmppOneToOneChat didReceiveChatMessage:(XMPPMessage *)message +NS_SWIFT_NAME(xmppOneToOneChat(_:didReceiveChatMessage:)); + +/// Notifies the delegate that a chat message has been sent in the stream. +- (void)xmppOneToOneChat:(XMPPOneToOneChat *)xmppOneToOneChat didSendChatMessage:(XMPPMessage *)message +NS_SWIFT_NAME(xmppOneToOneChat(_:didSendChatMessage:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/OneToOneChat/XMPPOneToOneChat.m b/Extensions/OneToOneChat/XMPPOneToOneChat.m new file mode 100644 index 0000000000..e884bc45e6 --- /dev/null +++ b/Extensions/OneToOneChat/XMPPOneToOneChat.m @@ -0,0 +1,40 @@ +#import "XMPPOneToOneChat.h" +#import "XMPPMessage.h" +#import "XMPPStream.h" +#import "XMPPLogging.h" + +// Log levels: off, error, warn, info, verbose +// Log flags: trace +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +@implementation XMPPOneToOneChat + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message +{ + XMPPLogTrace(); + + if (![message isChatMessage]) { + return; + } + + XMPPLogInfo(@"Received chat message from %@", [message from]); + [multicastDelegate xmppOneToOneChat:self didReceiveChatMessage:message]; +} + +- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message +{ + XMPPLogTrace(); + + if (![message isChatMessage]) { + return; + } + + XMPPLogInfo(@"Sent chat message to %@", [message to]); + [multicastDelegate xmppOneToOneChat:self didSendChatMessage:message]; +} + +@end diff --git a/Extensions/ProcessOne/XMPPProcessOne.h b/Extensions/ProcessOne/XMPPProcessOne.h index 7e4eb1e9aa..cbbf987b03 100644 --- a/Extensions/ProcessOne/XMPPProcessOne.h +++ b/Extensions/ProcessOne/XMPPProcessOne.h @@ -12,6 +12,7 @@ * * This file implements the client side functionality for XMPPFramework. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPProcessOne : XMPPModule /** @@ -20,9 +21,9 @@ * * If the session information is available, and the server supports rebind, fast reconnect may be possible. **/ -@property (strong, readwrite) NSString *savedSessionID; -@property (strong, readwrite) XMPPJID *savedSessionJID; -@property (strong, readwrite) NSDate *savedSessionDate; +@property (strong, readwrite, nullable) NSString *savedSessionID; +@property (strong, readwrite, nullable) XMPPJID *savedSessionJID; +@property (strong, readwrite, nullable) NSDate *savedSessionDate; /** * Push Mode Configuration. @@ -56,7 +57,7 @@ * * @see pushConfigurationContainer **/ -@property (readwrite, strong) NSXMLElement *pushConfiguration; +@property (readwrite, strong, nullable) NSXMLElement *pushConfiguration; /** * Standby Mode. @@ -85,11 +86,11 @@ * Methods to help create the pushConfiguration required to enable anything on the server. **/ -+ (NSXMLElement *)pushConfigurationContainer; +@property (class, readonly) NSXMLElement *pushConfigurationContainer; + (NSXMLElement *)keepaliveWithMax:(NSTimeInterval)max; + (NSXMLElement *)sessionWithDuration:(NSTimeInterval)duration; -+ (NSXMLElement *)statusWithType:(NSString *)type message:(NSString *)message; ++ (NSXMLElement *)statusWithType:(nullable NSString *)type message:(nullable NSString *)message; @end @@ -99,7 +100,11 @@ @interface XMPPRebindAuthentication : NSObject -- (id)initWithStream:(XMPPStream *)stream sessionID:(NSString *)sessionID sessionJID:(XMPPJID *)sessionJID; +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithStream:(XMPPStream *)stream + sessionID:(NSString *)sessionID + sessionJID:(XMPPJID *)sessionJID; @end @@ -109,9 +114,13 @@ @interface XMPPStream (XMPPProcessOne) -- (BOOL)supportsPush; -- (BOOL)supportsRebind; +/** Specific to XMPPProcessOne propreitary module */ +@property (nonatomic, readonly) BOOL supportsPush; +/** Specific to XMPPProcessOne propreitary module */ +@property (nonatomic, readonly) BOOL supportsRebind; -- (NSString *)rebindSessionID; +/** Specific to XMPPProcessOne propreitary module */ +@property (nonatomic, readonly, nullable) NSString *rebindSessionID; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/ProcessOne/XMPPProcessOne.m b/Extensions/ProcessOne/XMPPProcessOne.m index 2ebbae4b71..90d7a16f9c 100644 --- a/Extensions/ProcessOne/XMPPProcessOne.m +++ b/Extensions/ProcessOne/XMPPProcessOne.m @@ -94,7 +94,7 @@ - (NSXMLElement *)pushConfiguration __block NSXMLElement *result = nil; dispatch_sync(moduleQueue, ^{ - result = [pushConfiguration copy]; + result = [self->pushConfiguration copy]; }); return result; @@ -107,16 +107,16 @@ - (void)setPushConfiguration:(NSXMLElement *)pushConfig dispatch_block_t block = ^{ - if (pushConfiguration == nil && newPushConfiguration == nil) + if (self->pushConfiguration == nil && newPushConfiguration == nil) { return; } - pushConfiguration = newPushConfiguration; - pushConfigurationSent = NO; - pushConfigurationConfirmed = NO; + self->pushConfiguration = newPushConfiguration; + self->pushConfigurationSent = NO; + self->pushConfigurationConfirmed = NO; - if ([xmppStream isAuthenticated]) + if ([self->xmppStream isAuthenticated]) { [self sendPushConfiguration]; } @@ -148,7 +148,7 @@ - (void)sendPushConfiguration NSXMLElement *disable = [NSXMLElement elementWithName:@"disable" xmlns:@"p1:push"]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" child:disable]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:[XMPPStream generateUUID] child:disable]; [xmppStream sendElement:iq]; pushConfigurationSent = YES; @@ -311,7 +311,7 @@ - (BOOL)start:(NSError **)errPtr if (!sessionID || !sessionJID) { NSString *errMsg = @"Missing sessionID and/or sessionJID."; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info]; @@ -339,11 +339,11 @@ - (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)response { if ([[response name] isEqualToString:@"rebind"]) { - return XMPP_AUTH_SUCCESS; + return XMPPHandleAuthResponseSuccess; } else { - return XMPP_AUTH_FAIL; + return XMPPHandleAuthResponseFailed; } } diff --git a/Extensions/Reconnect/XMPPReconnect.h b/Extensions/Reconnect/XMPPReconnect.h index b13d1906d3..79b7f148f2 100644 --- a/Extensions/Reconnect/XMPPReconnect.h +++ b/Extensions/Reconnect/XMPPReconnect.h @@ -50,26 +50,8 @@ * which will trigger the class into action just as if an accidental disconnect occurred. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPReconnect : XMPPModule -{ - Byte flags; - Byte config; - NSTimeInterval reconnectDelay; - - dispatch_source_t reconnectTimer; - NSTimeInterval reconnectTimerInterval; - - SCNetworkReachabilityRef reachability; - - int reconnectTicket; - -#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_5 - SCNetworkConnectionFlags previousReachabilityFlags; -#else - SCNetworkReachabilityFlags previousReachabilityFlags; -#endif -} - /** * Whether auto reconnect is enabled or disabled. * @@ -181,3 +163,4 @@ #endif @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/Reconnect/XMPPReconnect.m b/Extensions/Reconnect/XMPPReconnect.m index f8dbed45ca..337c4ad446 100644 --- a/Extensions/Reconnect/XMPPReconnect.m +++ b/Extensions/Reconnect/XMPPReconnect.m @@ -18,10 +18,10 @@ enum XMPPReconnectFlags { - kShouldReconnect = 1 << 0, // If set, disconnection was accidental, and autoReconnect may be used - kMultipleChanges = 1 << 1, // If set, there have been reachability changes during a connection attempt - kManuallyStarted = 1 << 2, // If set, we were started manually via manualStart method - kQueryingDelegates = 1 << 3, // If set, we are awaiting response(s) from the delegate(s) + kShouldReconnect = 1 << 0, // If set, disconnection was accidental, and autoReconnect may be used + kShouldRestartReconnect = 1 << 1, // If set, another reconnection will be attempted after the current one fails + kManuallyStarted = 1 << 2, // If set, we were started manually via manualStart method + kQueryingDelegates = 1 << 3, // If set, we are awaiting response(s) from the delegate(s) }; enum XMPPReconnectConfig @@ -34,6 +34,26 @@ typedef SCNetworkConnectionFlags SCNetworkReachabilityFlags; #endif +@interface XMPPReconnect() { + Byte flags; + Byte config; + NSTimeInterval reconnectDelay; + + dispatch_source_t reconnectTimer; + NSTimeInterval reconnectTimerInterval; + + SCNetworkReachabilityRef reachability; + + int reconnectTicket; + +#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_5 + SCNetworkConnectionFlags previousReachabilityFlags; +#else + SCNetworkReachabilityFlags previousReachabilityFlags; +#endif +} +@end + @interface XMPPReconnect (PrivateAPI) - (void)setupReconnectTimer; @@ -103,7 +123,7 @@ - (BOOL)autoReconnect __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kAutoReconnect) ? YES : NO; + result = (self->config & kAutoReconnect) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -118,9 +138,9 @@ - (void)setAutoReconnect:(BOOL)flag { dispatch_block_t block = ^{ if (flag) - config |= kAutoReconnect; + self->config |= kAutoReconnect; else - config &= ~kAutoReconnect; + self->config &= ~kAutoReconnect; }; if (dispatch_get_specific(moduleQueueTag)) @@ -146,21 +166,21 @@ - (void)setShouldReconnect:(BOOL)flag flags &= ~kShouldReconnect; } -- (BOOL)multipleReachabilityChanges +- (BOOL)shouldRestartReconnect { NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue"); - return (flags & kMultipleChanges) ? YES : NO; + return (flags & kShouldRestartReconnect) ? YES : NO; } -- (void)setMultipleReachabilityChanges:(BOOL)flag +- (void)setShouldRestartReconnect:(BOOL)flag { NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue"); if (flag) - flags |= kMultipleChanges; + flags |= kShouldRestartReconnect; else - flags &= ~kMultipleChanges; + flags &= ~kShouldRestartReconnect; } - (BOOL)manuallyStarted @@ -205,7 +225,7 @@ - (void)manualStart { dispatch_block_t block = ^{ @autoreleasepool { - if ([xmppStream isDisconnected] && [self manuallyStarted] == NO) + if ([self->xmppStream isDisconnected] && [self manuallyStarted] == NO) { [self setManuallyStarted:YES]; @@ -226,11 +246,11 @@ - (void)stop // Clear all flags to disable any further reconnect attemts regardless of the state we're in. - flags = 0; + self->flags = 0; // Stop any planned reconnect attempts and stop monitoring the network. - reconnectTicket++; + self->reconnectTicket++; [self teardownReconnectTimer]; [self teardownNetworkMonitoring]; @@ -263,7 +283,7 @@ - (void)xmppStreamDidConnect:(XMPPStream *)sender // the stream opens but prior to authentication completing. // If this happens we still want to abide by the previous shouldReconnect setting. - [self setMultipleReachabilityChanges:NO]; + [self setShouldRestartReconnect:NO]; [self setManuallyStarted:NO]; reconnectTicket++; @@ -330,7 +350,7 @@ - (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error [multicastDelegate xmppReconnect:self didDetectAccidentalDisconnect:reachabilityFlags]; } - if ([self multipleReachabilityChanges]) + if ([self shouldRestartReconnect]) { // While the previous connection attempt was in progress, the reachability of the xmpp host changed. // This means that while the previous attempt failed, an attempt now might succeed. @@ -358,6 +378,15 @@ static void XMPPReconnectReachabilityCallback(SCNetworkReachabilityRef target, S @autoreleasepool { XMPPReconnect *instance = (__bridge XMPPReconnect *)info; + + if (instance->previousReachabilityFlags != flags) { + // It's possible that the reachability of our xmpp host has changed in the middle of either + // a reconnection attempt or while querying our delegates for permission to attempt reconnect. + + // In such case it makes sense to abort the current reconnection attempt (if any) instead of waiting for it to time out. + [instance setShouldRestartReconnect:YES]; + } + [instance maybeAttemptReconnectWithReachabilityFlags:flags]; } } @@ -382,6 +411,10 @@ - (void)setupReconnectTimer dispatch_source_set_event_handler(reconnectTimer, ^{ @autoreleasepool { + // It is likely that reconnectTimerInterval is shorter than socket's timeout value. + // In such case we want to abort the current connection attempt (if any) and start a new one. + [self setShouldRestartReconnect:YES]; + [self maybeAttemptReconnect]; }}); @@ -588,34 +621,37 @@ - (void)maybeAttemptReconnectWithReachabilityFlags:(SCNetworkReachabilityFlags)r shouldAttemptReconnect = (dispatch_semaphore_wait(delSemaphore, DISPATCH_TIME_NOW) != 0); } - dispatch_async(moduleQueue, ^{ @autoreleasepool { + dispatch_async(self->moduleQueue, ^{ @autoreleasepool { [self setQueryingDelegates:NO]; if (shouldAttemptReconnect) { - [self setMultipleReachabilityChanges:NO]; - previousReachabilityFlags = reachabilityFlags; + [self setShouldRestartReconnect:NO]; + self->previousReachabilityFlags = reachabilityFlags; if (self.usesOldSchoolSecureConnect) { - [xmppStream oldSchoolSecureConnectWithTimeout:XMPPStreamTimeoutNone error:nil]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self->xmppStream oldSchoolSecureConnectWithTimeout:XMPPStreamTimeoutNone error:nil]; +#pragma clang diagnostic pop } else { - [xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:nil]; + [self->xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:nil]; } } - else if ([self multipleReachabilityChanges]) + else if ([self shouldRestartReconnect]) { - [self setMultipleReachabilityChanges:NO]; - previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS; + [self setShouldRestartReconnect:NO]; + self->previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS; [self maybeAttemptReconnect]; } else { - previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS; + self->previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS; } }}); @@ -631,19 +667,10 @@ - (void)maybeAttemptReconnectWithReachabilityFlags:(SCNetworkReachabilityFlags)r { // The xmpp stream is already attempting a connection. - if (reachabilityFlags != previousReachabilityFlags) - { - // It seems that the reachability of our xmpp host has changed in the middle of either - // a reconnection attempt or while querying our delegates for permission to attempt reconnect. - // - // This may mean that the current attempt will fail, - // but an another attempt after the failure will succeed. - // - // We make a note of the multiple changes, - // and if the current attempt fails, we'll try again after a short delay. - - [self setMultipleReachabilityChanges:YES]; - } + if ([self shouldRestartReconnect]) + { + [xmppStream abortConnecting]; + } } } } diff --git a/Extensions/Roster/CoreDataStorage/XMPPGroupCoreDataStorageObject.m b/Extensions/Roster/CoreDataStorage/XMPPGroupCoreDataStorageObject.m index 50373ea166..0dbf1421f3 100644 --- a/Extensions/Roster/CoreDataStorage/XMPPGroupCoreDataStorageObject.m +++ b/Extensions/Roster/CoreDataStorage/XMPPGroupCoreDataStorageObject.m @@ -73,7 +73,7 @@ + (id)fetchOrInsertGroupName:(NSString *)groupName inManagedObjectContext:(NSMan if ([results count] > 0) { - return [results objectAtIndex:0]; + return results[0]; } return [self insertGroupName:groupName inManagedObjectContext:moc]; diff --git a/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.h b/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.h index 2a71fa73fa..397c217efe 100644 --- a/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.h +++ b/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.h @@ -12,8 +12,8 @@ @property (nonatomic, strong) XMPPJID *jid; @property (nonatomic, strong) XMPPPresence *presence; -@property (nonatomic, assign) int priority; -@property (nonatomic, assign) int intShow; +@property (nonatomic, assign) NSInteger priority; +@property (nonatomic, assign) XMPPPresenceShow intShow; @property (nonatomic, strong) NSString * jidStr; @property (nonatomic, strong) NSString * presenceStr; diff --git a/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.m b/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.m index c00060cfba..b4ee99a955 100644 --- a/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.m +++ b/Extensions/Roster/CoreDataStorage/XMPPResourceCoreDataStorageObject.m @@ -87,24 +87,24 @@ - (void)setPresence:(XMPPPresence *)newPresence self.presenceStr = [newPresence compactXMLString]; } -- (int)priority +- (NSInteger)priority { return [[self priorityNum] intValue]; } -- (void)setPriority:(int)priority +- (void)setPriority:(NSInteger)priority { - self.priorityNum = [NSNumber numberWithInt:priority]; + self.priorityNum = @(priority); } -- (int)intShow +- (XMPPPresenceShow)intShow { return [[self showNum] intValue]; } -- (void)setIntShow:(int)intShow +- (void)setIntShow:(XMPPPresenceShow)intShow { - self.showNum = [NSNumber numberWithInt:intShow]; + self.showNum = @(intShow); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -172,7 +172,7 @@ - (void)updateWithPresence:(XMPPPresence *)presence self.presence = presence; self.priority = [presence priority]; - self.intShow = [presence intShow]; + self.intShow = [presence showValue]; self.type = [presence type]; self.show = [presence show]; @@ -188,8 +188,8 @@ - (NSComparisonResult)compare:(id )another XMPPPresence *mp = [self presence]; XMPPPresence *ap = [another presence]; - int mpp = [mp priority]; - int app = [ap priority]; + NSInteger mpp = [mp priority]; + NSInteger app = [ap priority]; if(mpp < app) return NSOrderedDescending; @@ -198,8 +198,8 @@ - (NSComparisonResult)compare:(id )another // Priority is the same. // Determine who is more available based on their show. - int mps = [mp intShow]; - int aps = [ap intShow]; + XMPPPresenceShow mps = [mp showValue]; + XMPPPresenceShow aps = [ap showValue]; if(mps < aps) return NSOrderedDescending; diff --git a/Extensions/Roster/CoreDataStorage/XMPPRosterCoreDataStorage.m b/Extensions/Roster/CoreDataStorage/XMPPRosterCoreDataStorage.m index c8b286c5a5..8101757b20 100644 --- a/Extensions/Roster/CoreDataStorage/XMPPRosterCoreDataStorage.m +++ b/Extensions/Roster/CoreDataStorage/XMPPRosterCoreDataStorage.m @@ -260,13 +260,13 @@ - (XMPPResourceCoreDataStorageObject *)resourceForJID:(XMPPJID *)jid #pragma mark Protocol Private API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream +- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version { XMPPLogTrace(); [self scheduleBlock:^{ - [rosterPopulationSet addObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]]; + [self->rosterPopulationSet addObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]]; // Clear anything already in the roster core data store. // @@ -280,7 +280,7 @@ - (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:entity]; - [fetchRequest setFetchBatchSize:saveThreshold]; + [fetchRequest setFetchBatchSize:self->saveThreshold]; if (stream) { @@ -308,7 +308,7 @@ - (void)endRosterPopulationForXMPPStream:(XMPPStream *)stream [self scheduleBlock:^{ - [rosterPopulationSet removeObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]]; + [self->rosterPopulationSet removeObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]]; }]; } @@ -324,7 +324,7 @@ - (void)handleRosterItem:(NSXMLElement *)itemSubElement xmppStream:(XMPPStream * NSManagedObjectContext *moc = [self managedObjectContext]; - if ([rosterPopulationSet containsObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]]) + if ([self->rosterPopulationSet containsObject:[NSNumber xmpp_numberWithPtr:(__bridge void *)stream]]) { NSString *streamBareJidStr = [[self myJIDForXMPPStream:stream] bare]; @@ -369,6 +369,8 @@ - (void)handleRosterItem:(NSXMLElement *)itemSubElement xmppStream:(XMPPStream * - (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream { XMPPLogTrace(); + + BOOL allowRosterlessOperation = [parent allowRosterlessOperation]; [self scheduleBlock:^{ @@ -379,7 +381,7 @@ - (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc]; - if (user == nil && [parent allowRosterlessOperation]) + if (user == nil && allowRosterlessOperation) { // This may happen if the roster is in rosterlessOperation mode. @@ -455,7 +457,7 @@ - (void)clearAllUsersAndResourcesForXMPPStream:(XMPPStream *)stream NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:entity]; - [fetchRequest setFetchBatchSize:saveThreshold]; + [fetchRequest setFetchBatchSize:self->saveThreshold]; if (stream) { @@ -474,7 +476,7 @@ - (void)clearAllUsersAndResourcesForXMPPStream:(XMPPStream *)stream { [moc deleteObject:user]; - if (++unsavedCount >= saveThreshold) + if (++unsavedCount >= self->saveThreshold) { [self save]; unsavedCount = 0; @@ -500,7 +502,7 @@ - (NSArray *)jidsForXMPPStream:(XMPPStream *)stream{ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:entity]; - [fetchRequest setFetchBatchSize:saveThreshold]; + [fetchRequest setFetchBatchSize:self->saveThreshold]; if (stream) { @@ -522,5 +524,52 @@ - (NSArray *)jidsForXMPPStream:(XMPPStream *)stream{ return results; } +- (void)getSubscription:(NSString * _Nullable __autoreleasing * _Nullable)subscription + ask:(NSString * _Nullable __autoreleasing * _Nullable)ask + nickname:(NSString * _Nullable __autoreleasing * _Nullable)nickname + groups:(NSArray * _Nullable __autoreleasing * _Nullable)groups + forJID:(XMPPJID *)jid + xmppStream:(XMPPStream *)stream +{ + XMPPLogTrace(); + + [self executeBlock:^{ + + NSManagedObjectContext *moc = [self managedObjectContext]; + XMPPUserCoreDataStorageObject *user = [self userForJID:jid xmppStream:stream managedObjectContext:moc]; + + if(user) + { + if(subscription) + { + *subscription = user.subscription; + } + + if(ask) + { + *ask = user.ask; + } + + if(nickname) + { + *nickname = user.nickname; + } + + if(groups) + { + if([user.groups count]) + { + NSMutableArray *groupNames = [NSMutableArray array]; + + for(XMPPGroupCoreDataStorageObject *group in user.groups){ + [groupNames addObject:group.name]; + } + + *groups = groupNames; + } + } + } + }]; +} @end diff --git a/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.h b/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.h index 3604aac365..29cfb3d1fe 100644 --- a/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.h +++ b/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.h @@ -1,15 +1,17 @@ #import #import -#if !TARGET_OS_IPHONE - #import +#if TARGET_OS_IPHONE + #import +#else + #import #endif #import "XMPPUser.h" #import "XMPP.h" +#import "XMPPResourceCoreDataStorageObject.h" @class XMPPGroupCoreDataStorageObject; -@class XMPPResourceCoreDataStorageObject; @interface XMPPUserCoreDataStorageObject : NSManagedObject diff --git a/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.m b/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.m index 370754f343..f7f9b82610 100644 --- a/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.m +++ b/Extensions/Roster/CoreDataStorage/XMPPUserCoreDataStorageObject.m @@ -108,7 +108,7 @@ - (NSInteger)section - (void)setSection:(NSInteger)value { - self.sectionNum = [NSNumber numberWithInteger:value]; + self.sectionNum = @(value); } - (NSInteger)primitiveSection @@ -291,7 +291,7 @@ - (void)recalculatePrimaryResource NSArray *sortedResources = [[self allResources] sortedArrayUsingSelector:@selector(compare:)]; if ([sortedResources count] > 0) { - XMPPResourceCoreDataStorageObject *resource = [sortedResources objectAtIndex:0]; + XMPPResourceCoreDataStorageObject *resource = sortedResources[0]; // Primary resource must have a non-negative priority if ([resource priority] >= 0) diff --git a/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.h b/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.h index ffc7a745d1..9999102073 100644 --- a/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.h +++ b/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.h @@ -6,7 +6,7 @@ @class XMPPPresence; -@interface XMPPResourceMemoryStorageObject : NSObject +@interface XMPPResourceMemoryStorageObject : NSObject { XMPPJID *jid; XMPPPresence *presence; diff --git a/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.m b/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.m index a8ec4e0666..dea7526e25 100644 --- a/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.m +++ b/Extensions/Roster/MemoryStorage/XMPPResourceMemoryStorageObject.m @@ -63,9 +63,19 @@ - (id)initWithCoder:(NSCoder *)coder { if([coder allowsKeyedCoding]) { - jid = [coder decodeObjectForKey:@"jid"]; - presence = [coder decodeObjectForKey:@"presence"]; - presenceDate = [coder decodeObjectForKey:@"presenceDate"]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"]; + presence = [coder decodeObjectOfClass:[XMPPPresence class] forKey:@"presence"]; + presenceDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"presenceDate"]; + } + else + { + jid = [coder decodeObjectForKey:@"jid"]; + presence = [coder decodeObjectForKey:@"presence"]; + presenceDate = [coder decodeObjectForKey:@"presenceDate"]; + } } else { @@ -93,6 +103,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Standard Methods //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -136,8 +151,8 @@ - (NSComparisonResult)compare:(id )another XMPPPresence *mp = [self presence]; XMPPPresence *ap = [another presence]; - int mpp = [mp priority]; - int app = [ap priority]; + NSInteger mpp = [mp priority]; + NSInteger app = [ap priority]; if(mpp < app) return NSOrderedDescending; @@ -146,8 +161,8 @@ - (NSComparisonResult)compare:(id )another // Priority is the same. // Determine who is more available based on their show. - int mps = [mp intShow]; - int aps = [ap intShow]; + XMPPPresenceShow mps = [mp showValue]; + XMPPPresenceShow aps = [ap showValue]; if(mps < aps) return NSOrderedDescending; diff --git a/Extensions/Roster/MemoryStorage/XMPPRosterMemoryStorage.m b/Extensions/Roster/MemoryStorage/XMPPRosterMemoryStorage.m index 71437d9f2e..40ef0c181f 100644 --- a/Extensions/Roster/MemoryStorage/XMPPRosterMemoryStorage.m +++ b/Extensions/Roster/MemoryStorage/XMPPRosterMemoryStorage.m @@ -122,7 +122,7 @@ - (XMPPUserMemoryStorageObject *)_userForJID:(XMPPJID *)jid { AssertPrivateQueue(); - XMPPUserMemoryStorageObject *result = [roster objectForKey:[jid bareJID]]; + XMPPUserMemoryStorageObject *result = roster[[jid bareJID]]; if (result) { @@ -279,7 +279,7 @@ - (XMPPUserMemoryStorageObject *)myUser __block XMPPUserMemoryStorageObject *result; dispatch_sync(parentQueue, ^{ - result = [myUser copy]; + result = [self->myUser copy]; }); return result; @@ -306,7 +306,7 @@ - (XMPPResourceMemoryStorageObject *)myResource dispatch_sync(parentQueue, ^{ XMPPResourceMemoryStorageObject *resource = - (XMPPResourceMemoryStorageObject *)[myUser resourceForJID:myJID]; + (XMPPResourceMemoryStorageObject *)[self->myUser resourceForJID:self->myJID]; result = [resource copy]; }); @@ -610,7 +610,7 @@ - (NSArray *)sortedResources:(BOOL)includeResourcesForMyUserExcludingMyself #pragma mark XMPPRosterStorage Protocol //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream +- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version { XMPPLogTrace(); AssertParentQueue(); @@ -646,7 +646,7 @@ - (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream XMPPUserMemoryStorageObject *newUser = (XMPPUserMemoryStorageObject *)[[self.userClass alloc] initWithItem:item]; - [roster setObject:newUser forKey:jid]; + roster[jid] = newUser; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); } @@ -656,7 +656,7 @@ - (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream if ([subscription isEqualToString:@"remove"]) { - XMPPUserMemoryStorageObject *user = [roster objectForKey:jid]; + XMPPUserMemoryStorageObject *user = roster[jid]; if (user) { [roster removeObjectForKey:jid]; @@ -669,7 +669,7 @@ - (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream } else { - XMPPUserMemoryStorageObject *user = [roster objectForKey:jid]; + XMPPUserMemoryStorageObject *user = roster[jid]; if (user) { [user updateWithItem:item]; @@ -684,7 +684,7 @@ - (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream XMPPUserMemoryStorageObject *newUser = (XMPPUserMemoryStorageObject *)[[self.userClass alloc] initWithItem:item]; - [roster setObject:newUser forKey:jid]; + roster[jid] = newUser; XMPPLogVerbose(@"roster(%lu): %@", (unsigned long)[roster count], roster); @@ -708,7 +708,7 @@ - (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream XMPPJID *jidKey = [[presence from] bareJID]; - user = [roster objectForKey:jidKey]; + user = roster[jidKey]; if (user == nil) { // Not a presence element from anyone in our roster (that we know of). @@ -726,7 +726,7 @@ - (void)handlePresence:(XMPPPresence *)presence xmppStream:(XMPPStream *)stream user = (XMPPUserMemoryStorageObject *)[[self.userClass alloc] initWithJID:jidKey]; - [roster setObject:user forKey:jidKey]; + roster[jidKey] = user; [[self multicastDelegate] xmppRoster:self didAddUser:user]; [[self multicastDelegate] xmppRosterDidChange:self]; @@ -756,7 +756,7 @@ - (BOOL)userExistsWithJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream AssertParentQueue(); XMPPJID *jidKey = [jid bareJID]; - XMPPUserMemoryStorageObject *rosterUser = [roster objectForKey:jidKey]; + XMPPUserMemoryStorageObject *rosterUser = roster[jidKey]; return (rosterUser != nil); } @@ -771,7 +771,7 @@ - (void)setPhoto:(NSImage *)photo forUserWithJID:(XMPPJID *)jid xmppStream:(XMPP AssertParentQueue(); XMPPJID *jidKey = [jid bareJID]; - XMPPUserMemoryStorageObject *rosterUser = [roster objectForKey:jidKey]; + XMPPUserMemoryStorageObject *rosterUser = roster[jidKey]; if (rosterUser) { @@ -819,4 +819,42 @@ - (NSArray *)jidsForXMPPStream:(XMPPStream *)stream return results; } +- (void)getSubscription:(NSString * _Nullable * _Nullable)subscription + ask:(NSString * _Nullable * _Nullable)ask + nickname:(NSString * _Nullable * _Nullable)nickname + groups:(NSArray * _Nullable * _Nullable)groups + forJID:(XMPPJID *)jid + xmppStream:(XMPPStream *)stream +{ + + XMPPLogTrace(); + AssertParentQueue(); + + XMPPJID *jidKey = [jid bareJID]; + XMPPUserMemoryStorageObject *rosterUser = roster[jidKey]; + + if(rosterUser) + { + if(subscription) + { + *subscription = rosterUser.subscription; + } + + if(ask) + { + *ask = rosterUser.ask; + } + + if(nickname) + { + *nickname = rosterUser.nickname; + } + + if(groups) + { + *groups = rosterUser.groups; + } + } +} + @end diff --git a/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.h b/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.h index 001734f4f8..3841df9a8f 100644 --- a/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.h +++ b/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.h @@ -2,17 +2,20 @@ #import "XMPPUser.h" #import "XMPP.h" -#if !TARGET_OS_IPHONE - #import +#if TARGET_OS_IPHONE + #import +#else + #import #endif @class XMPPResourceMemoryStorageObject; -@interface XMPPUserMemoryStorageObject : NSObject +@interface XMPPUserMemoryStorageObject : NSObject { XMPPJID *jid; NSMutableDictionary *itemAttributes; + NSMutableArray *groups; NSMutableDictionary *resources; XMPPResourceMemoryStorageObject *primaryResource; @@ -39,6 +42,10 @@ */ +- (NSString *)subscription; + +- (NSString *)ask; + /** * Simple convenience method. * If a nickname exists for the user, the nickname is returned. @@ -46,6 +53,11 @@ **/ - (NSString *)displayName; +/** + * An array of Group Names. +**/ +- (NSArray *)groups; + /** * If XMPPvCardAvatarModule is included in the framework, the XMPPRoster will automatically integrate with it, * and we'll save the the user photos after they've been downloaded. diff --git a/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.m b/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.m index 7ea68fec53..641fc9c2b9 100644 --- a/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.m +++ b/Extensions/Roster/MemoryStorage/XMPPUserMemoryStorageObject.m @@ -33,6 +33,8 @@ - (id)initWithJID:(XMPPJID *)aJid itemAttributes = [[NSMutableDictionary alloc] initWithCapacity:0]; + groups = [[NSMutableArray alloc] initWithCapacity:0]; + [self commonInit]; } return self; @@ -45,7 +47,20 @@ - (id)initWithItem:(NSXMLElement *)item NSString *jidStr = [item attributeStringValueForName:@"jid"]; jid = [[XMPPJID jidWithString:jidStr] bareJID]; - itemAttributes = [item attributesAsDictionary]; + itemAttributes = [[item attributesAsDictionary] mutableCopy]; + + groups = [[NSMutableArray alloc] initWithCapacity:0]; + + NSArray *groupElements = [item elementsForName:@"group"]; + + for (NSXMLElement *groupElement in groupElements) { + NSString *groupName = [groupElement stringValue]; + + if ([groupName length]) + { + [groups addObject:groupName]; + } + } [self commonInit]; } @@ -65,14 +80,15 @@ - (id)copyWithZone:(NSZone *)zone deepCopy->jid = [jid copy]; deepCopy->itemAttributes = [itemAttributes mutableCopy]; + deepCopy->groups = [groups mutableCopy]; deepCopy->resources = [[NSMutableDictionary alloc] initWithCapacity:[resources count]]; for (XMPPJID *key in resources) { - XMPPResourceMemoryStorageObject *resourceCopy = [[resources objectForKey:key] copy]; + XMPPResourceMemoryStorageObject *resourceCopy = [resources[key] copy]; - [deepCopy->resources setObject:resourceCopy forKey:key]; + deepCopy->resources[key] = resourceCopy; } [deepCopy recalculatePrimaryResource]; @@ -101,20 +117,39 @@ - (id)initWithCoder:(NSCoder *)coder { if ([coder allowsKeyedCoding]) { - jid = [coder decodeObjectForKey:@"jid"]; - itemAttributes = [[coder decodeObjectForKey:@"itemAttributes"] mutableCopy]; - #if TARGET_OS_IPHONE - photo = [[UIImage alloc] initWithData:[coder decodeObjectForKey:@"photo"]]; - #else - photo = [[NSImage alloc] initWithData:[coder decodeObjectForKey:@"photo"]]; - #endif - resources = [[coder decodeObjectForKey:@"resources"] mutableCopy]; - primaryResource = [coder decodeObjectForKey:@"primaryResource"]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"]; + itemAttributes = [[coder decodeObjectOfClass:[NSDictionary class] forKey:@"itemAttributes"] mutableCopy]; + groups = [[coder decodeObjectOfClass:[NSArray class] forKey:@"groups"] mutableCopy]; +#if TARGET_OS_IPHONE + photo = [[UIImage alloc] initWithData:[coder decodeObjectOfClass:[NSData class] forKey:@"photo"]]; +#else + photo = [[NSImage alloc] initWithData:[coder decodeObjectOfClass:[NSData class] forKey:@"photo"]]; +#endif + resources = [[coder decodeObjectOfClass:[NSDictionary class] forKey:@"resources"] mutableCopy]; + primaryResource = [coder decodeObjectOfClass:[XMPPResourceMemoryStorageObject class] forKey:@"primaryResource"]; + } + else + { + jid = [coder decodeObjectForKey:@"jid"]; + itemAttributes = [[coder decodeObjectForKey:@"itemAttributes"] mutableCopy]; + groups = [[coder decodeObjectForKey:@"groups"] mutableCopy]; +#if TARGET_OS_IPHONE + photo = [[UIImage alloc] initWithData:[coder decodeObjectForKey:@"photo"]]; +#else + photo = [[NSImage alloc] initWithData:[coder decodeObjectForKey:@"photo"]]; +#endif + resources = [[coder decodeObjectForKey:@"resources"] mutableCopy]; + primaryResource = [coder decodeObjectForKey:@"primaryResource"]; + } } else { jid = [coder decodeObject]; itemAttributes = [[coder decodeObject] mutableCopy]; + groups = [[coder decodeObject] mutableCopy]; #if TARGET_OS_IPHONE photo = [[UIImage alloc] initWithData:[coder decodeObject]]; #else @@ -133,6 +168,7 @@ - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:jid forKey:@"jid"]; [coder encodeObject:itemAttributes forKey:@"itemAttributes"]; + [coder encodeObject:groups forKey:@"groups"]; #if TARGET_OS_IPHONE [coder encodeObject:UIImagePNGRepresentation(photo) forKey:@"photo"]; #else @@ -145,6 +181,7 @@ - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:jid]; [coder encodeObject:itemAttributes]; + [coder encodeObject:groups]; #if TARGET_OS_IPHONE [coder encodeObject:UIImagePNGRepresentation(photo)]; #else @@ -155,6 +192,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Standard Methods //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -168,7 +210,17 @@ - (XMPPJID *)jid - (NSString *)nickname { - return (NSString *)[itemAttributes objectForKey:@"name"]; + return (NSString *) itemAttributes[@"name"]; +} + +- (NSString *)subscription +{ + return (NSString *) itemAttributes[@"subscription"]; +} + +- (NSString *)ask +{ + return (NSString *) itemAttributes[@"ask"]; } - (NSString *)displayName @@ -180,6 +232,11 @@ - (NSString *)displayName return [jid bare]; } +- (NSArray *)groups +{ + return [groups copy]; +} + - (BOOL)isOnline { return (primaryResource != nil); @@ -191,8 +248,8 @@ - (BOOL)isPendingApproval // // - NSString *subscription = [itemAttributes objectForKey:@"subscription"]; - NSString *ask = [itemAttributes objectForKey:@"ask"]; + NSString *subscription = itemAttributes[@"subscription"]; + NSString *ask = itemAttributes[@"ask"]; if ([subscription isEqualToString:@"none"] || [subscription isEqualToString:@"from"]) { @@ -212,7 +269,7 @@ - (BOOL)isPendingApproval - (id )resourceForJID:(XMPPJID *)aJid { - return [resources objectForKey:aJid]; + return resources[aJid]; } - (NSArray *)allResources @@ -256,7 +313,7 @@ - (void)recalculatePrimaryResource NSArray *sortedResources = [[self allResources] sortedArrayUsingSelector:@selector(compare:)]; if ([sortedResources count] > 0) { - XMPPResourceMemoryStorageObject *possiblePrimary = [sortedResources objectAtIndex:0]; + XMPPResourceMemoryStorageObject *possiblePrimary = sortedResources[0]; // Primary resource must have a non-negative priority if ([[possiblePrimary presence] priority] >= 0) @@ -286,7 +343,20 @@ - (void)updateWithItem:(NSXMLElement *)item NSString *key = [node name]; NSString *value = [node stringValue]; - [itemAttributes setObject:value forKey:key]; + itemAttributes[key] = value; + } + + [groups removeAllObjects]; + + NSArray *groupElements = [item elementsForName:@"group"]; + + for (NSXMLElement *groupElement in groupElements) { + NSString *groupName = [groupElement stringValue]; + + if ([groupName length]) + { + [groups addObject:groupName]; + } } } @@ -302,7 +372,7 @@ - (int)updateWithPresence:(XMPPPresence *)presence if ([presenceType isEqualToString:@"unavailable"] || [presenceType isEqualToString:@"error"]) { - resource = [resources objectForKey:key]; + resource = resources[key]; if (resource) { [resources removeObjectForKey:key]; @@ -313,7 +383,7 @@ - (int)updateWithPresence:(XMPPPresence *)presence } else { - resource = [resources objectForKey:key]; + resource = resources[key]; if (resource) { [self willUpdateResource:resource withPresence:presence]; @@ -326,7 +396,7 @@ - (int)updateWithPresence:(XMPPPresence *)presence { resource = (XMPPResourceMemoryStorageObject *)[[resourceClass alloc] initWithPresence:presence]; - [resources setObject:resource forKey:key]; + resources[key] = resource; [self didAddResource:resource withPresence:presence]; result = XMPP_USER_ADDED_RESOURCE; diff --git a/Extensions/Roster/XMPPResource.h b/Extensions/Roster/XMPPResource.h index 43ee55579e..80247aeb53 100644 --- a/Extensions/Roster/XMPPResource.h +++ b/Extensions/Roster/XMPPResource.h @@ -1,15 +1,16 @@ #import #import "XMPP.h" - -@protocol XMPPResource +NS_ASSUME_NONNULL_BEGIN +@protocol XMPPResource @required -- (XMPPJID *)jid; -- (XMPPPresence *)presence; +@property (nonatomic, readonly) XMPPJID *jid; +@property (nonatomic, readonly) XMPPPresence *presence; -- (NSDate *)presenceDate; +@property (nonatomic, readonly) NSDate *presenceDate; - (NSComparisonResult)compare:(id )another; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/Roster/XMPPRoster.h b/Extensions/Roster/XMPPRoster.h index 5a89eebc35..70d0b7f770 100644 --- a/Extensions/Roster/XMPPRoster.h +++ b/Extensions/Roster/XMPPRoster.h @@ -1,7 +1,9 @@ #import -#if !TARGET_OS_IPHONE - #import +#if TARGET_OS_IPHONE + #import +#else + #import #endif #import "XMPP.h" @@ -32,6 +34,7 @@ * * If you use XMPPvCardAvatarModule, the roster will automatically support user photos. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPRoster : XMPPModule { /* Inherited from XMPPModule: @@ -53,8 +56,10 @@ DDList *mucModules; } -- (id)initWithRosterStorage:(id )storage; -- (id)initWithRosterStorage:(id )storage dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; +- (instancetype)initWithRosterStorage:(id )storage; +- (instancetype)initWithRosterStorage:(id )storage dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; /* Inherited from XMPPModule: @@ -151,14 +156,14 @@ * The roster has either been requested manually (fetchRoster:) * or automatically (autoFetchRoster) but has yet to be populated. **/ -@property (assign, getter = hasRequestedRoster, readonly) BOOL requestedRoster; +@property (assign, readonly) BOOL hasRequestedRoster; /** * The initial roster has been received by client and is currently being populated. - * @see xmppRosterDidBeginPopulating: + * @see xmppRosterDidBeginPopulating:withVersion: * @see xmppRosterDidEndPopulating: **/ -@property (assign, getter = isPopulating, readonly) BOOL populating; +@property (assign, readonly) BOOL isPopulating; /** * The initial roster has been received by client and populated. @@ -170,26 +175,27 @@ * Useful if you disable autoFetchRoster. **/ - (void)fetchRoster; +- (void)fetchRosterVersion:(nullable NSString *)version; /** * Adds the given user to the roster with an optional nickname * and requests permission to receive presence information from them. **/ -- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName; +- (void)addUser:(XMPPJID *)jid withNickname:(nullable NSString *)optionalName; /** * Adds the given user to the roster with an optional nickname, * adds the given user to groups * and requests permission to receive presence information from them. **/ -- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSArray *)groups; +- (void)addUser:(XMPPJID *)jid withNickname:(nullable NSString *)optionalName groups:(nullable NSArray *)groups; /** * Adds the given user to the roster with an optional nickname, * adds the given user to groups * and optionally requests permission to receive presence information from them. **/ -- (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSArray *)groups subscribeToPresence:(BOOL)subscribe; +- (void)addUser:(XMPPJID *)jid withNickname:(nullable NSString *)optionalName groups:(nullable NSArray *)groups subscribeToPresence:(BOOL)subscribe; /** * Sets/modifies the nickname for the given user. @@ -320,7 +326,7 @@ **/ - (BOOL)configureWithParent:(XMPPRoster *)aParent queue:(dispatch_queue_t)queue; -- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream; +- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version; - (void)endRosterPopulationForXMPPStream:(XMPPStream *)stream; - (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream; @@ -331,7 +337,14 @@ - (void)clearAllResourcesForXMPPStream:(XMPPStream *)stream; - (void)clearAllUsersAndResourcesForXMPPStream:(XMPPStream *)stream; -- (NSArray *)jidsForXMPPStream:(XMPPStream *)stream; +- (NSArray *)jidsForXMPPStream:(XMPPStream *)stream; + +- (void)getSubscription:(NSString * _Nullable * _Nullable)subscription + ask:(NSString * _Nullable * _Nullable)ask + nickname:(NSString * _Nullable * _Nullable)nickname + groups:(NSArray * _Nullable * _Nullable)groups + forJID:(XMPPJID *)jid + xmppStream:(XMPPStream *)stream; @optional @@ -375,7 +388,7 @@ /** * Sent when the initial roster is received. **/ -- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender; +- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender withVersion:(NSString *)version; /** * Sent when the initial roster has been populated into storage. @@ -394,3 +407,5 @@ - (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(NSXMLElement *)item; @end +NS_ASSUME_NONNULL_END + diff --git a/Extensions/Roster/XMPPRoster.m b/Extensions/Roster/XMPPRoster.m index b4b70db4dd..3231668c09 100644 --- a/Extensions/Roster/XMPPRoster.m +++ b/Extensions/Roster/XMPPRoster.m @@ -43,16 +43,6 @@ - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)prese @implementation XMPPRoster -- (id)init -{ - return [self initWithRosterStorage:nil dispatchQueue:NULL]; -} - -- (id)initWithDispatchQueue:(dispatch_queue_t)queue -{ - return [self initWithRosterStorage:nil dispatchQueue:queue]; -} - - (id)initWithRosterStorage:(id )storage { return [self initWithRosterStorage:storage dispatchQueue:NULL]; @@ -111,7 +101,7 @@ - (BOOL)activate:(XMPPStream *)aXmppStream if ([module isKindOfClass:[XMPPMUC class]]) { - [mucModules add:(__bridge void *)module]; + [self->mucModules add:(__bridge void *)module]; } }]; } @@ -129,8 +119,8 @@ - (void)deactivate dispatch_block_t block = ^{ @autoreleasepool { - [xmppIDTracker removeAllIDs]; - xmppIDTracker = nil; + [self->xmppIDTracker removeAllIDs]; + self->xmppIDTracker = nil; }}; @@ -177,7 +167,7 @@ - (BOOL)autoFetchRoster __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kAutoFetchRoster) ? YES : NO; + result = (self->config & kAutoFetchRoster) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -193,9 +183,9 @@ - (void)setAutoFetchRoster:(BOOL)flag dispatch_block_t block = ^{ if (flag) - config |= kAutoFetchRoster; + self->config |= kAutoFetchRoster; else - config &= ~kAutoFetchRoster; + self->config &= ~kAutoFetchRoster; }; if (dispatch_get_specific(moduleQueueTag)) @@ -209,7 +199,7 @@ - (BOOL)autoClearAllUsersAndResources __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kAutoClearAllUsersAndResources) ? YES : NO; + result = (self->config & kAutoClearAllUsersAndResources) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -225,9 +215,9 @@ - (void)setAutoClearAllUsersAndResources:(BOOL)flag dispatch_block_t block = ^{ if (flag) - config |= kAutoClearAllUsersAndResources; + self->config |= kAutoClearAllUsersAndResources; else - config &= ~kAutoClearAllUsersAndResources; + self->config &= ~kAutoClearAllUsersAndResources; }; if (dispatch_get_specific(moduleQueueTag)) @@ -241,7 +231,7 @@ - (BOOL)autoAcceptKnownPresenceSubscriptionRequests __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kAutoAcceptKnownPresenceSubscriptionRequests) ? YES : NO; + result = (self->config & kAutoAcceptKnownPresenceSubscriptionRequests) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -257,9 +247,9 @@ - (void)setAutoAcceptKnownPresenceSubscriptionRequests:(BOOL)flag dispatch_block_t block = ^{ if (flag) - config |= kAutoAcceptKnownPresenceSubscriptionRequests; + self->config |= kAutoAcceptKnownPresenceSubscriptionRequests; else - config &= ~kAutoAcceptKnownPresenceSubscriptionRequests; + self->config &= ~kAutoAcceptKnownPresenceSubscriptionRequests; }; if (dispatch_get_specific(moduleQueueTag)) @@ -273,7 +263,7 @@ - (BOOL)allowRosterlessOperation __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kRosterlessOperation) ? YES : NO; + result = (self->config & kRosterlessOperation) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -289,9 +279,9 @@ - (void)setAllowRosterlessOperation:(BOOL)flag dispatch_block_t block = ^{ if (flag) - config |= kRosterlessOperation; + self->config |= kRosterlessOperation; else - config &= ~kRosterlessOperation; + self->config &= ~kRosterlessOperation; }; if (dispatch_get_specific(moduleQueueTag)) @@ -306,7 +296,7 @@ - (BOOL)hasRequestedRoster __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kRequestedRoster) ? YES : NO; + result = (self->flags & kRequestedRoster) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -322,7 +312,7 @@ - (BOOL)isPopulating{ __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kPopulatingRoster) ? YES : NO; + result = (self->flags & kPopulatingRoster) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -338,7 +328,7 @@ - (BOOL)hasRoster __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kHasRoster) ? YES : NO; + result = (self->flags & kHasRoster) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -515,9 +505,7 @@ - (void)addUser:(XMPPJID *)jid withNickname:(NSString *)optionalName groups:(NSA NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"]; [query addChild:item]; - NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"]; - [iq addAttributeWithName:@"type" stringValue:@"set"]; - [iq addChild:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" child:query]; [xmppStream sendElement:iq]; @@ -680,11 +668,25 @@ - (void)rejectPresenceSubscriptionRequestFrom:(XMPPJID *)jid XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribed" to:[jid bareJID]]; [xmppStream sendElement:presence]; } - - (void)fetchRoster { // This is a public method, so it may be invoked on any thread/queue. + dispatch_block_t block = ^{ @autoreleasepool { + + [self fetchRosterVersion:nil]; + + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} +- (void)fetchRosterVersion:(NSString *)version +{ + // This is a public method, so it may be invoked on any thread/queue. + dispatch_block_t block = ^{ @autoreleasepool { if ([self _requestedRoster]) @@ -694,20 +696,22 @@ - (void)fetchRoster } // - // + // // NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:roster"]; + if (version) + [query addAttributeWithName:@"ver" stringValue:version]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" elementID:[xmppStream generateUUID]]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" elementID:[self->xmppStream generateUUID]]; [iq addChild:query]; - [xmppIDTracker addElement:iq + [self->xmppIDTracker addElement:iq target:self selector:@selector(handleFetchRosterQueryIQ:withInfo:) timeout:60]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; [self _setRequestedRoster:YES]; }}; @@ -727,15 +731,16 @@ - (void)handleFetchRosterQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *) dispatch_block_t block = ^{ @autoreleasepool { NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:roster"]; + NSString * version = [query attributeStringValueForName:@"ver"]; BOOL hasRoster = [self hasRoster]; if (!hasRoster) { - [xmppRosterStorage clearAllUsersAndResourcesForXMPPStream:xmppStream]; - [self _setPopulatingRoster:YES]; - [multicastDelegate xmppRosterDidBeginPopulating:self]; - [xmppRosterStorage beginRosterPopulationForXMPPStream:xmppStream]; + [self->xmppRosterStorage clearAllUsersAndResourcesForXMPPStream:self->xmppStream]; + [self _setPopulatingRoster:YES]; + [self->multicastDelegate xmppRosterDidBeginPopulating:self withVersion:version]; + [self->xmppRosterStorage beginRosterPopulationForXMPPStream:self->xmppStream withVersion:version]; } NSArray *items = [query elementsForName:@"item"]; @@ -746,18 +751,18 @@ - (void)handleFetchRosterQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *) // We should have our roster now [self _setHasRoster:YES]; - [self _setPopulatingRoster:NO]; - [multicastDelegate xmppRosterDidEndPopulating:self]; - [xmppRosterStorage endRosterPopulationForXMPPStream:xmppStream]; + [self _setPopulatingRoster:NO]; + [self->multicastDelegate xmppRosterDidEndPopulating:self]; + [self->xmppRosterStorage endRosterPopulationForXMPPStream:self->xmppStream]; // Process any premature presence elements we received. - for (XMPPPresence *presence in earlyPresenceElements) + for (XMPPPresence *presence in self->earlyPresenceElements) { - [self xmppStream:xmppStream didReceivePresence:presence]; + [self xmppStream:self->xmppStream didReceivePresence:presence]; } - [earlyPresenceElements removeAllObjects]; + [self->earlyPresenceElements removeAllObjects]; } }}; diff --git a/Extensions/Roster/XMPPUser.h b/Extensions/Roster/XMPPUser.h index 1a2d6922dd..f0869fb2a8 100644 --- a/Extensions/Roster/XMPPUser.h +++ b/Extensions/Roster/XMPPUser.h @@ -3,19 +3,20 @@ @protocol XMPPResource; - -@protocol XMPPUser +NS_ASSUME_NONNULL_BEGIN +@protocol XMPPUser @required -- (XMPPJID *)jid; -- (NSString *)nickname; +@property (nonatomic, readonly) XMPPJID *jid; +@property (nonatomic, readonly) NSString *nickname; -- (BOOL)isOnline; -- (BOOL)isPendingApproval; +@property (nonatomic, readonly) BOOL isOnline; +@property (nonatomic, readonly) BOOL isPendingApproval; -- (id )primaryResource; -- (id )resourceForJID:(XMPPJID *)jid; +@property (nonatomic, readonly, nullable) id primaryResource; +- (nullable id )resourceForJID:(XMPPJID *)jid; -- (NSArray *)allResources; +@property (nonatomic, readonly) NSArray> *allResources; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0009/XMPPIQ+JabberRPC.m b/Extensions/XEP-0009/XMPPIQ+JabberRPC.m index e711f30bcb..c170d7e044 100644 --- a/Extensions/XEP-0009/XMPPIQ+JabberRPC.m +++ b/Extensions/XEP-0009/XMPPIQ+JabberRPC.m @@ -143,7 +143,7 @@ +(NSXMLElement *)valueElementFromDictionary:(NSDictionary *)dictionary { member = [NSXMLElement elementWithName:@"member"]; name = [NSXMLElement elementWithName:@"name" stringValue:key]; [member addChild:name]; - [member addChild:[self valueElementFromObject:[dictionary objectForKey:key]]]; + [member addChild:[self valueElementFromObject:dictionary[key]]]; } return [self wrapValueElementAroundElement:structElement]; diff --git a/Extensions/XEP-0009/XMPPIQ+JabberRPCResonse.m b/Extensions/XEP-0009/XMPPIQ+JabberRPCResonse.m index c9f081bdf7..3048170f8b 100644 --- a/Extensions/XEP-0009/XMPPIQ+JabberRPCResonse.m +++ b/Extensions/XEP-0009/XMPPIQ+JabberRPCResonse.m @@ -229,7 +229,7 @@ -(NSDictionary *)parseMember:(NSXMLElement *)memberElement { NSString *key = [self objectFromElement:[memberElement elementForName:@"name"]]; id value = [self objectFromElement:[memberElement elementForName:@"value"]]; - return [NSDictionary dictionaryWithObject:value forKey:key]; + return @{key : value}; } #pragma mark - @@ -247,19 +247,19 @@ - (NSDate *)parseDateString: (NSString *)dateString withFormat: (NSString *)form #pragma mark - - (NSNumber *)parseInteger: (NSString *)value { - return [NSNumber numberWithInteger: [value integerValue]]; + return @([value integerValue]); } - (NSNumber *)parseDouble: (NSString *)value { - return [NSNumber numberWithDouble: [value doubleValue]]; + return @([value doubleValue]); } - (NSNumber *)parseBoolean: (NSString *)value { if ([value isEqualToString: @"1"]) { - return [NSNumber numberWithBool: YES]; + return @YES; } - return [NSNumber numberWithBool: NO]; + return @NO; } - (NSString *)parseString: (NSString *)value { diff --git a/Extensions/XEP-0009/XMPPJabberRPCModule.m b/Extensions/XEP-0009/XMPPJabberRPCModule.m index 54c03dd74c..dd20bf4aca 100644 --- a/Extensions/XEP-0009/XMPPJabberRPCModule.m +++ b/Extensions/XEP-0009/XMPPJabberRPCModule.m @@ -107,7 +107,7 @@ - (NSTimeInterval)defaultTimeout __block NSTimeInterval result; dispatch_block_t block = ^{ - result = defaultTimeout; + result = self->defaultTimeout; }; if (dispatch_get_specific(moduleQueueTag)) @@ -122,7 +122,7 @@ - (void)setDefaultTimeout:(NSTimeInterval)newDefaultTimeout { dispatch_block_t block = ^{ XMPPLogTrace(); - defaultTimeout = newDefaultTimeout; + self->defaultTimeout = newDefaultTimeout; }; if (dispatch_get_specific(moduleQueueTag)) @@ -214,7 +214,7 @@ - (NSString *)sendRpcIQ:(XMPPIQ *)iq withTimeout:(NSTimeInterval)timeout RPCID *rpcID = [[RPCID alloc] initWithRpcID:elementID timer:timer]; - [rpcIDs setObject:rpcID forKey:elementID]; + rpcIDs[elementID] = rpcID; [xmppStream sendElement:iq]; @@ -225,16 +225,15 @@ - (void)timeoutRemoveRpcID:(NSString *)elementID { XMPPLogTrace(); - RPCID *rpcID = [rpcIDs objectForKey:elementID]; + RPCID *rpcID = rpcIDs[elementID]; if (rpcID) { [rpcID cancelTimer]; [rpcIDs removeObjectForKey:elementID]; NSError *error = [NSError errorWithDomain:XMPPJabberRPCErrorDomain - code:1400 - userInfo:[NSDictionary dictionaryWithObjectsAndKeys: - @"Request timed out", @"error",nil]]; + code:1400 + userInfo:@{@"error" : @"Request timed out"}]; [multicastDelegate jabberRPC:self elementID:elementID didReceiveError:error]; } @@ -262,7 +261,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq // we could check the query element, but we should be able to do a lookup based on the unique elementID // because we send an ID, we should get one back - RPCID *rpcID = [rpcIDs objectForKey:elementID]; + RPCID *rpcID = rpcIDs[elementID]; if (rpcID == nil) { return NO; @@ -290,13 +289,12 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq // TODO: implement error parsing // not much specified in XEP, only 403 forbidden error NSXMLElement *errorElement = [iq childErrorElement]; - NSError *error = [NSError errorWithDomain:XMPPJabberRPCErrorDomain - code:[errorElement attributeIntValueForName:@"code"] - userInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [errorElement attributesAsDictionary],@"error", - [[errorElement childAtIndex:0] name], @"condition", - iq,@"iq", - nil]]; + NSError *error = [NSError errorWithDomain:XMPPJabberRPCErrorDomain + code:[errorElement attributeIntValueForName:@"code"] + userInfo:@{ + @"error" : [errorElement attributesAsDictionary], + @"condition" : [[errorElement childAtIndex:0] name], + @"iq" : iq}]; [multicastDelegate jabberRPC:self elementID:elementID didReceiveError:error]; } diff --git a/Extensions/XEP-0012/XMPPLastActivity.m b/Extensions/XEP-0012/XMPPLastActivity.m index a5acb82c45..2dcb0a8c11 100644 --- a/Extensions/XEP-0012/XMPPLastActivity.m +++ b/Extensions/XEP-0012/XMPPLastActivity.m @@ -68,8 +68,8 @@ - (void)deactivate #endif dispatch_block_t block = ^{ @autoreleasepool { - [_queryTracker removeAllIDs]; - _queryTracker = nil; + [self->_queryTracker removeAllIDs]; + self->_queryTracker = nil; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -91,7 +91,7 @@ - (BOOL)respondsToQueries __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = _respondsToQueries; + result = self->_respondsToQueries; }); return result; @@ -101,14 +101,14 @@ - (BOOL)respondsToQueries - (void)setRespondsToQueries:(BOOL)respondsToQueries { dispatch_block_t block = ^{ - if (_respondsToQueries != respondsToQueries) + if (self->_respondsToQueries != respondsToQueries) { - _respondsToQueries = respondsToQueries; + self->_respondsToQueries = respondsToQueries; #ifdef _XMPP_CAPABILITIES_H @autoreleasepool { // Capabilities may have changed, need to notify others - [xmppStream resendMyPresence]; + [self->xmppStream resendMyPresence]; } #endif } @@ -132,7 +132,7 @@ - (NSString *)sendLastActivityQueryToJID:(XMPPJID *)jid withTimeout:(NSTimeInter dispatch_async(moduleQueue, ^{ __weak __typeof__(self) self_weak_ = self; - [_queryTracker addID:queryID block:^(XMPPIQ *iq, id info) { + [self->_queryTracker addID:queryID block:^(XMPPIQ *iq, id info) { __strong __typeof__(self) self = self_weak_; if (iq) { @@ -144,7 +144,7 @@ - (NSString *)sendLastActivityQueryToJID:(XMPPJID *)jid withTimeout:(NSTimeInter } } timeout:timeout]; - [xmppStream sendElement:query]; + [self->xmppStream sendElement:query]; }); return queryID; diff --git a/Extensions/XEP-0016/XMPPPrivacy.h b/Extensions/XEP-0016/XMPPPrivacy.h index e6c68c5977..186cbc2146 100644 --- a/Extensions/XEP-0016/XMPPPrivacy.h +++ b/Extensions/XEP-0016/XMPPPrivacy.h @@ -1,9 +1,7 @@ #import #import "XMPPModule.h" -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; #define _XMPP_PRIVACY_H diff --git a/Extensions/XEP-0016/XMPPPrivacy.m b/Extensions/XEP-0016/XMPPPrivacy.m index 345211b069..a10096d391 100644 --- a/Extensions/XEP-0016/XMPPPrivacy.m +++ b/Extensions/XEP-0016/XMPPPrivacy.m @@ -124,7 +124,7 @@ - (BOOL)autoRetrievePrivacyListNames __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = autoRetrievePrivacyListNames; + result = self->autoRetrievePrivacyListNames; }); return result; @@ -135,7 +135,7 @@ - (void)setAutoRetrievePrivacyListNames:(BOOL)flag { dispatch_block_t block = ^{ - autoRetrievePrivacyListNames = flag; + self->autoRetrievePrivacyListNames = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -155,7 +155,7 @@ - (BOOL)autoRetrievePrivacyListItems __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = autoRetrievePrivacyListItems; + result = self->autoRetrievePrivacyListItems; }); return result; @@ -166,7 +166,7 @@ - (void)setAutoRetrievePrivacyListItems:(BOOL)flag { dispatch_block_t block = ^{ - autoRetrievePrivacyListItems = flag; + self->autoRetrievePrivacyListItems = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -186,7 +186,7 @@ - (BOOL)autoClearPrivacyListInfo __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = autoClearPrivacyListInfo; + result = self->autoClearPrivacyListInfo; }); return result; @@ -197,7 +197,7 @@ - (void)setAutoClearPrivacyListInfo:(BOOL)flag { dispatch_block_t block = ^{ - autoClearPrivacyListInfo = flag; + self->autoClearPrivacyListInfo = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -268,7 +268,7 @@ - (void)clearPrivacyListInfo { dispatch_async(moduleQueue, ^{ @autoreleasepool { - [privacyDict removeAllObjects]; + [self->privacyDict removeAllObjects]; }}); } } @@ -285,7 +285,7 @@ - (NSArray *)listNames dispatch_sync(moduleQueue, ^{ @autoreleasepool { - result = [[privacyDict allKeys] copy]; + result = [[self->privacyDict allKeys] copy]; }}); return result; @@ -294,9 +294,9 @@ - (NSArray *)listNames - (NSArray *)listWithName:(NSString *)privacyListName { - NSArray* (^block)() = ^ NSArray* () { + NSArray* (^block)(void) = ^ NSArray* () { - id result = [privacyDict objectForKey:privacyListName]; + id result = self->privacyDict[privacyListName]; if (result == [NSNull null]) // Not fetched yet return nil; @@ -482,7 +482,7 @@ - (void)addQueryInfo:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid queryInfo.timer = timer; // Add to dictionary - [pendingQueries setObject:queryInfo forKey:uuid]; + pendingQueries[uuid] = queryInfo; } - (void)removeQueryInfo:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid @@ -522,7 +522,7 @@ - (void)processQuery:(XMPPPrivacyQueryInfo *)queryInfo withFailureCode:(XMPPPriv - (void)queryTimeout:(NSString *)uuid { - XMPPPrivacyQueryInfo *queryInfo = [privacyDict objectForKey:uuid]; + XMPPPrivacyQueryInfo *queryInfo = privacyDict[uuid]; if (queryInfo) { [self processQuery:queryInfo withFailureCode:XMPPPrivacyQueryTimeout]; @@ -615,10 +615,10 @@ - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPPrivacyQueryInfo *)query NSString *name = [listName attributeStringValueForName:@"name"]; if (name) { - id value = [privacyDict objectForKey:name]; + id value = privacyDict[name]; if (value == nil) { - [privacyDict setObject:[NSNull null] forKey:name]; + privacyDict[name] = [NSNull null]; } } } @@ -670,7 +670,7 @@ - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPPrivacyQueryInfo *)query items = [NSArray array]; } - [privacyDict setObject:items forKey:queryInfo.privacyListName]; + privacyDict[queryInfo.privacyListName] = items; [multicastDelegate xmppPrivacy:self didReceiveListWithName:queryInfo.privacyListName items:items]; } @@ -696,7 +696,7 @@ - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPPrivacyQueryInfo *)query items = [NSArray array]; } - [privacyDict setObject:items forKey:queryInfo.privacyListName]; + privacyDict[queryInfo.privacyListName] = items; [multicastDelegate xmppPrivacy:self didSetListWithName:queryInfo.privacyListName]; } @@ -832,7 +832,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { // This may be a response to a query we sent - XMPPPrivacyQueryInfo *queryInfo = [pendingQueries objectForKey:[iq elementID]]; + XMPPPrivacyQueryInfo *queryInfo = pendingQueries[[iq elementID]]; if (queryInfo) { [self processQueryResponse:iq withInfo:queryInfo]; @@ -841,7 +841,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { for (NSString *privacyListName in privacyDict) { - id privacyListItems = [privacyDict objectForKey:privacyListName]; + id privacyListItems = privacyDict[privacyListName]; if (privacyListItems == [NSNull null]) { @@ -864,7 +864,7 @@ -(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error for (NSString *uuid in pendingQueries) { - XMPPPrivacyQueryInfo *queryInfo = [privacyDict objectForKey:uuid]; + XMPPPrivacyQueryInfo *queryInfo = privacyDict[uuid]; [self processQuery:queryInfo withFailureCode:XMPPPrivacyDisconnect]; } diff --git a/Extensions/XEP-0045/CoreDataStorage/XMPPRoomCoreDataStorage.m b/Extensions/XEP-0045/CoreDataStorage/XMPPRoomCoreDataStorage.m index 6f3a6956ea..b147bfc592 100644 --- a/Extensions/XEP-0045/CoreDataStorage/XMPPRoomCoreDataStorage.m +++ b/Extensions/XEP-0045/CoreDataStorage/XMPPRoomCoreDataStorage.m @@ -101,7 +101,7 @@ - (NSString *)messageEntityName __block NSString *result = nil; dispatch_block_t block = ^{ - result = messageEntityName; + result = self->messageEntityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -115,7 +115,7 @@ - (NSString *)messageEntityName - (void)setMessageEntityName:(NSString *)newMessageEntityName { dispatch_block_t block = ^{ - messageEntityName = newMessageEntityName; + self->messageEntityName = newMessageEntityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -129,7 +129,7 @@ - (NSString *)occupantEntityName __block NSString *result = nil; dispatch_block_t block = ^{ - result = occupantEntityName; + result = self->occupantEntityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -143,7 +143,7 @@ - (NSString *)occupantEntityName - (void)setOccupantEntityName:(NSString *)newOccupantEntityName { dispatch_block_t block = ^{ - occupantEntityName = newOccupantEntityName; + self->occupantEntityName = newOccupantEntityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -157,7 +157,7 @@ - (NSTimeInterval)maxMessageAge __block NSTimeInterval result = 0; dispatch_block_t block = ^{ - result = maxMessageAge; + result = self->maxMessageAge; }; if (dispatch_get_specific(storageQueueTag)) @@ -172,10 +172,10 @@ - (void)setMaxMessageAge:(NSTimeInterval)age { dispatch_block_t block = ^{ @autoreleasepool { - NSTimeInterval oldMaxMessageAge = maxMessageAge; + NSTimeInterval oldMaxMessageAge = self->maxMessageAge; NSTimeInterval newMaxMessageAge = age; - maxMessageAge = age; + self->maxMessageAge = age; // There are several cases we need to handle here. // @@ -221,7 +221,7 @@ - (void)setMaxMessageAge:(NSTimeInterval)age { [self performDelete]; - if (deleteTimer) + if (self->deleteTimer) [self updateDeleteTimer]; else [self createAndStartDeleteTimer]; @@ -239,7 +239,7 @@ - (NSTimeInterval)deleteInterval __block NSTimeInterval result = 0; dispatch_block_t block = ^{ - result = deleteInterval; + result = self->deleteInterval; }; if (dispatch_get_specific(storageQueueTag)) @@ -254,7 +254,7 @@ - (void)setDeleteInterval:(NSTimeInterval)interval { dispatch_block_t block = ^{ @autoreleasepool { - deleteInterval = interval; + self->deleteInterval = interval; // There are several cases we need to handle here. // @@ -269,9 +269,9 @@ - (void)setDeleteInterval:(NSTimeInterval)interval // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. // (Plus we might need to do an immediate delete.) - if (deleteInterval > 0.0) + if (self->deleteInterval > 0.0) { - if (deleteTimer == NULL) + if (self->deleteTimer == NULL) { // Handles #2 // @@ -291,7 +291,7 @@ - (void)setDeleteInterval:(NSTimeInterval)interval [self updateDeleteTimer]; } } - else if (deleteTimer) + else if (self->deleteTimer) { // Handles #1 @@ -309,7 +309,7 @@ - (void)pauseOldMessageDeletionForRoom:(XMPPJID *)roomJID { dispatch_block_t block = ^{ @autoreleasepool { - [pausedMessageDeletion addObject:[roomJID bareJID]]; + [self->pausedMessageDeletion addObject:[roomJID bareJID]]; }}; if (dispatch_get_specific(storageQueueTag)) @@ -322,7 +322,7 @@ - (void)resumeOldMessageDeletionForRoom:(XMPPJID *)roomJID { dispatch_block_t block = ^{ @autoreleasepool { - [pausedMessageDeletion removeObject:[roomJID bareJID]]; + [self->pausedMessageDeletion removeObject:[roomJID bareJID]]; [self performDelete]; }}; @@ -818,15 +818,15 @@ - (NSDate *)mostRecentMessageTimestampForRoom:(XMPPJID *)roomJID NSString *streamBareJidStr = [[self myJIDForXMPPStream:xmppStream] bare]; NSString *predicateFormat = @"roomJIDStr == %@ AND streamBareJidStr == %@"; - predicate = [NSPredicate predicateWithFormat:predicateFormat, roomJID, streamBareJidStr]; + predicate = [NSPredicate predicateWithFormat:predicateFormat, roomJID.bare, streamBareJidStr]; } else { - predicate = [NSPredicate predicateWithFormat:@"roomJIDStr == %@", roomJID]; + predicate = [NSPredicate predicateWithFormat:@"roomJIDStr == %@", roomJID.bare]; } NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"localTimestamp" ascending:NO]; - NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; + NSArray *sortDescriptors = @[sortDescriptor]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:entity]; diff --git a/Extensions/XEP-0045/CoreDataStorage/XMPPRoomMessageCoreDataStorageObject.m b/Extensions/XEP-0045/CoreDataStorage/XMPPRoomMessageCoreDataStorageObject.m index 983ca4adcc..2d43565d20 100644 --- a/Extensions/XEP-0045/CoreDataStorage/XMPPRoomMessageCoreDataStorageObject.m +++ b/Extensions/XEP-0045/CoreDataStorage/XMPPRoomMessageCoreDataStorageObject.m @@ -150,7 +150,7 @@ - (BOOL)isFromMe - (void)setIsFromMe:(BOOL)value { - self.fromMe = [NSNumber numberWithBool:value]; + self.fromMe = @(value); } #pragma mark - Message diff --git a/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorage.m b/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorage.m index e68d7b7e5f..a1b8782d91 100644 --- a/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorage.m +++ b/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorage.m @@ -122,7 +122,7 @@ - (NSTimeInterval)maxMessageAge __block NSTimeInterval result = 0; dispatch_block_t block = ^{ - result = maxMessageAge; + result = self->maxMessageAge; }; if (dispatch_get_specific(storageQueueTag)) @@ -137,10 +137,10 @@ - (void)setMaxMessageAge:(NSTimeInterval)age { dispatch_block_t block = ^{ @autoreleasepool { - NSTimeInterval oldMaxMessageAge = maxMessageAge; + NSTimeInterval oldMaxMessageAge = self->maxMessageAge; NSTimeInterval newMaxMessageAge = age; - maxMessageAge = age; + self->maxMessageAge = age; // There are several cases we need to handle here. // @@ -186,7 +186,7 @@ - (void)setMaxMessageAge:(NSTimeInterval)age { [self performDelete]; - if (deleteTimer) + if (self->deleteTimer) [self updateDeleteTimer]; else [self createAndStartDeleteTimer]; @@ -204,7 +204,7 @@ - (NSTimeInterval)deleteInterval __block NSTimeInterval result = 0; dispatch_block_t block = ^{ - result = deleteInterval; + result = self->deleteInterval; }; if (dispatch_get_specific(storageQueueTag)) @@ -219,7 +219,7 @@ - (void)setDeleteInterval:(NSTimeInterval)interval { dispatch_block_t block = ^{ @autoreleasepool { - deleteInterval = interval; + self->deleteInterval = interval; // There are several cases we need to handle here. // @@ -234,9 +234,9 @@ - (void)setDeleteInterval:(NSTimeInterval)interval // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. // (Plus we might need to do an immediate delete.) - if (deleteInterval > 0.0) + if (self->deleteInterval > 0.0) { - if (deleteTimer == NULL) + if (self->deleteTimer == NULL) { // Handles #2 // @@ -256,7 +256,7 @@ - (void)setDeleteInterval:(NSTimeInterval)interval [self updateDeleteTimer]; } } - else if (deleteTimer) + else if (self->deleteTimer) { // Handles #1 @@ -274,7 +274,7 @@ - (void)pauseOldMessageDeletionForRoom:(XMPPJID *)roomJID { dispatch_block_t block = ^{ @autoreleasepool { - [pausedMessageDeletion addObject:[roomJID bareJID]]; + [self->pausedMessageDeletion addObject:[roomJID bareJID]]; }}; if (dispatch_get_specific(storageQueueTag)) @@ -287,7 +287,7 @@ - (void)resumeOldMessageDeletionForRoom:(XMPPJID *)roomJID { dispatch_block_t block = ^{ @autoreleasepool { - [pausedMessageDeletion removeObject:[roomJID bareJID]]; + [self->pausedMessageDeletion removeObject:[roomJID bareJID]]; [self performDelete]; }}; @@ -607,24 +607,24 @@ - (XMPPRoomOccupantHybridMemoryStorageObject *)insertOccupantWithPresence:(XMPPP XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream]; XMPPJID *roomJid = room.roomJID; - NSMutableDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; + NSMutableDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid]; if (occupantsRoomsDict == nil) { occupantsRoomsDict = [[NSMutableDictionary alloc] init]; - [occupantsGlobalDict setObject:occupantsRoomsDict forKey:streamFullJid]; + occupantsGlobalDict[streamFullJid] = occupantsRoomsDict; } - NSMutableDictionary *occupantsRoomDict = [occupantsRoomsDict objectForKey:roomJid]; + NSMutableDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid]; if (occupantsRoomDict == nil) { occupantsRoomDict = [[NSMutableDictionary alloc] init]; - [occupantsRoomsDict setObject:occupantsRoomDict forKey:roomJid]; + occupantsRoomsDict[roomJid] = occupantsRoomDict; } XMPPRoomOccupantHybridMemoryStorageObject *occupant = (XMPPRoomOccupantHybridMemoryStorageObject *) [[self.occupantClass alloc] initWithPresence:presence streamFullJid:streamFullJid]; - [occupantsRoomDict setObject:occupant forKey:occupant.jid]; + occupantsRoomDict[occupant.jid] = occupant; return occupant; } @@ -656,8 +656,8 @@ - (void)removeOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant XMPPJID *streamFullJid = [self myJIDForXMPPStream:stream]; XMPPJID *roomJid = occupant.roomJID; - NSMutableDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; - NSMutableDictionary *occupantsRoomDict = [occupantsRoomsDict objectForKey:roomJid]; + NSMutableDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid]; + NSMutableDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid]; [occupantsRoomDict removeObjectForKey:occupant.jid]; // Remove occupant if ([occupantsRoomDict count] == 0) @@ -715,7 +715,7 @@ - (NSDate *)mostRecentMessageTimestampForRoom:(XMPPJID *)roomJID } NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"localTimestamp" ascending:NO]; - NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; + NSArray *sortDescriptors = @[sortDescriptor]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:entity]; @@ -759,19 +759,19 @@ - (XMPPRoomOccupantHybridMemoryStorageObject *)occupantForJID:(XMPPJID *)occupan { XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream]; - NSDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; - NSDictionary *occupantsRoomDict = [occupantsRoomsDict objectForKey:roomJid]; + NSDictionary *occupantsRoomsDict = self->occupantsGlobalDict[streamFullJid]; + NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid]; - occupant = [occupantsRoomDict objectForKey:occupantJid]; + occupant = occupantsRoomDict[occupantJid]; } else { - for (XMPPJID *streamFullJid in occupantsGlobalDict) + for (XMPPJID *streamFullJid in self->occupantsGlobalDict) { - NSDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; - NSDictionary *occupantsRoomDict = [occupantsRoomsDict objectForKey:roomJid]; + NSDictionary *occupantsRoomsDict = self->occupantsGlobalDict[streamFullJid]; + NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid]; - occupant = [occupantsRoomDict objectForKey:occupantJid]; + occupant = occupantsRoomDict[occupantJid]; if (occupant) break; } } @@ -794,7 +794,7 @@ - (NSArray *)occupantsForRoom:(XMPPJID *)roomJid stream:(XMPPStream *)xmppStream { roomJid = [roomJid bareJID]; // Just in case a full jid is accidentally passed - __block NSArray *results = nil; + __block NSArray *results = @[]; void (^block)(BOOL) = ^(BOOL shouldCopy){ @autoreleasepool { @@ -802,17 +802,17 @@ - (NSArray *)occupantsForRoom:(XMPPJID *)roomJid stream:(XMPPStream *)xmppStream { XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream]; - NSDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; - NSDictionary *occupantsRoomDict = [occupantsRoomsDict objectForKey:roomJid]; + NSDictionary *occupantsRoomsDict = self->occupantsGlobalDict[streamFullJid]; + NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid]; results = [occupantsRoomDict allValues]; } else { - for (XMPPJID *streamFullJid in occupantsGlobalDict) + for (XMPPJID *streamFullJid in self->occupantsGlobalDict) { - NSDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; - NSDictionary *occupantsRoomDict = [occupantsRoomsDict objectForKey:roomJid]; + NSDictionary *occupantsRoomsDict = self->occupantsGlobalDict[streamFullJid]; + NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid]; if (occupantsRoomDict) { @@ -981,7 +981,7 @@ - (void)handleDidLeaveRoom:(XMPPRoom *)room XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream]; - NSMutableDictionary *occupantsRoomsDict = [occupantsGlobalDict objectForKey:streamFullJid]; + NSMutableDictionary *occupantsRoomsDict = self->occupantsGlobalDict[streamFullJid]; [occupantsRoomsDict removeObjectForKey:roomJid]; // Remove room (and all associated occupants) }]; diff --git a/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorageProtected.h b/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorageProtected.h index e5b2c94bfb..bc01f1eaea 100644 --- a/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorageProtected.h +++ b/Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorageProtected.h @@ -9,6 +9,8 @@ * E.g. [super insertOccupant...] **/ +#import "XMPPRoomHybridStorage.h" + @interface XMPPRoomHybridStorage (Protected) /** diff --git a/Extensions/XEP-0045/HybridStorage/XMPPRoomMessageHybridCoreDataStorageObject.m b/Extensions/XEP-0045/HybridStorage/XMPPRoomMessageHybridCoreDataStorageObject.m index c4eb758c81..b64d032093 100644 --- a/Extensions/XEP-0045/HybridStorage/XMPPRoomMessageHybridCoreDataStorageObject.m +++ b/Extensions/XEP-0045/HybridStorage/XMPPRoomMessageHybridCoreDataStorageObject.m @@ -150,7 +150,7 @@ - (BOOL)isFromMe - (void)setIsFromMe:(BOOL)value { - self.fromMe = [NSNumber numberWithBool:value]; + self.fromMe = @(value); } #pragma mark - Message diff --git a/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.h b/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.h index 01f64db747..65c37c615f 100644 --- a/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.h +++ b/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.h @@ -3,7 +3,7 @@ #import "XMPPRoomOccupant.h" -@interface XMPPRoomOccupantHybridMemoryStorageObject : NSObject +@interface XMPPRoomOccupantHybridMemoryStorageObject : NSObject - (id)initWithPresence:(XMPPPresence *)presence streamFullJid:(XMPPJID *)streamFullJid; - (void)updateWithPresence:(XMPPPresence *)presence; diff --git a/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.m b/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.m index 0f94753b9e..965b224a66 100644 --- a/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.m +++ b/Extensions/XEP-0045/HybridStorage/XMPPRoomOccupantHybridMemoryStorageObject.m @@ -45,10 +45,21 @@ - (id)initWithCoder:(NSCoder *)coder { if ([coder allowsKeyedCoding]) { - presence = [coder decodeObjectForKey:@"presence"]; - jid = [coder decodeObjectForKey:@"jid"]; - createdAt = [coder decodeObjectForKey:@"createdAt"]; - streamFullJid = [coder decodeObjectForKey:@"streamFullJid"]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + presence = [coder decodeObjectOfClass:[XMPPPresence class] forKey:@"presence"]; + jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"]; + createdAt = [coder decodeObjectOfClass:[NSDate class] forKey:@"createdAt"]; + streamFullJid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"streamFullJid"]; + } + else + { + presence = [coder decodeObjectForKey:@"presence"]; + jid = [coder decodeObjectForKey:@"jid"]; + createdAt = [coder decodeObjectForKey:@"createdAt"]; + streamFullJid = [coder decodeObjectForKey:@"streamFullJid"]; + } } else { @@ -79,6 +90,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Copying //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Extensions/XEP-0045/MemoryStorage/XMPPRoomMemoryStorage.m b/Extensions/XEP-0045/MemoryStorage/XMPPRoomMemoryStorage.m index 8764c4ee1b..87a4edfd2b 100644 --- a/Extensions/XEP-0045/MemoryStorage/XMPPRoomMemoryStorage.m +++ b/Extensions/XEP-0045/MemoryStorage/XMPPRoomMemoryStorage.m @@ -202,7 +202,7 @@ - (BOOL)existsMessage:(XMPPMessage *)message while (YES) { mid = (min + max) / 2; - XMPPRoomMessageMemoryStorageObject *currentMessage = [messages objectAtIndex:mid]; + XMPPRoomMessageMemoryStorageObject *currentMessage = messages[mid]; NSComparisonResult cmp = [minLocalTimestamp compare:[currentMessage localTimestamp]]; if (cmp == NSOrderedAscending) @@ -240,7 +240,7 @@ - (BOOL)existsMessage:(XMPPMessage *)message NSInteger index; for (index = mid; index < [messages count]; index++) { - XMPPRoomMessageMemoryStorageObject *currentMessage = [messages objectAtIndex:index]; + XMPPRoomMessageMemoryStorageObject *currentMessage = messages[index]; NSComparisonResult cmp = [maxLocalTimestamp compare:[currentMessage localTimestamp]]; if (cmp != NSOrderedAscending) @@ -296,7 +296,7 @@ - (NSUInteger)insertMessage:(XMPPRoomMessageMemoryStorageObject *)message // Shortcut - Most (if not all) messages are inserted at the end - XMPPRoomMessageMemoryStorageObject *lastMessage = [messages objectAtIndex:(count - 1)]; + XMPPRoomMessageMemoryStorageObject *lastMessage = messages[count - 1]; if ([message compare:lastMessage] != NSOrderedAscending) { [messages addObject:message]; @@ -313,7 +313,7 @@ - (NSUInteger)insertMessage:(XMPPRoomMessageMemoryStorageObject *)message while (YES) { mid = (min + max) / 2; - XMPPRoomMessageMemoryStorageObject *currentMessage = [messages objectAtIndex:mid]; + XMPPRoomMessageMemoryStorageObject *currentMessage = messages[mid]; NSComparisonResult cmp = [message compare:currentMessage]; if (cmp == NSOrderedAscending) @@ -369,7 +369,7 @@ - (void)addMessage:(XMPPRoomMessageMemoryStorageObject *)roomMsg { NSUInteger index = [self insertMessage:roomMsg]; - XMPPRoomOccupantMemoryStorageObject *occupant = [occupantsDict objectForKey:[roomMsg jid]]; + XMPPRoomOccupantMemoryStorageObject *occupant = occupantsDict[[roomMsg jid]]; XMPPRoomMessageMemoryStorageObject *roomMsgCopy = [roomMsg copy]; XMPPRoomOccupantMemoryStorageObject *occupantCopy = [occupant copy]; @@ -401,7 +401,7 @@ - (NSUInteger)insertOccupant:(XMPPRoomOccupantMemoryStorageObject *)occupant while (YES) { mid = (min + max) / 2; - XMPPRoomOccupantMemoryStorageObject *currentOccupant = [occupantsArray objectAtIndex:mid]; + XMPPRoomOccupantMemoryStorageObject *currentOccupant = occupantsArray[mid]; NSComparisonResult cmp = [occupant compare:currentOccupant]; if (cmp == NSOrderedAscending) @@ -447,7 +447,7 @@ - (XMPPRoomOccupantMemoryStorageObject *)occupantForJID:(XMPPJID *)jid if (dispatch_get_specific(parentQueueTag)) { - return [occupantsDict objectForKey:jid]; + return occupantsDict[jid]; } else { @@ -455,7 +455,7 @@ - (XMPPRoomOccupantMemoryStorageObject *)occupantForJID:(XMPPJID *)jid dispatch_sync(parentQueue, ^{ @autoreleasepool { - occupant = [[occupantsDict objectForKey:jid] copy]; + occupant = [self->occupantsDict[jid] copy]; }}); return occupant; @@ -482,7 +482,7 @@ - (NSArray *)messages dispatch_sync(parentQueue, ^{ @autoreleasepool { - result = [[NSArray alloc] initWithArray:messages copyItems:YES]; + result = [[NSArray alloc] initWithArray:self->messages copyItems:YES]; }}); return result; @@ -509,7 +509,7 @@ - (NSArray *)occupants dispatch_sync(parentQueue, ^{ @autoreleasepool { - result = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES]; + result = [[NSArray alloc] initWithArray:self->occupantsArray copyItems:YES]; }}); return result; @@ -537,8 +537,8 @@ - (NSArray *)resortMessages dispatch_sync(parentQueue, ^{ @autoreleasepool { - [messages sortUsingSelector:@selector(compare:)]; - result = [[NSArray alloc] initWithArray:messages copyItems:YES]; + [self->messages sortUsingSelector:@selector(compare:)]; + result = [[NSArray alloc] initWithArray:self->messages copyItems:YES]; }}); return result; @@ -566,8 +566,8 @@ - (NSArray *)resortOccupants dispatch_sync(parentQueue, ^{ @autoreleasepool { - [occupantsArray sortUsingSelector:@selector(compare:)]; - result = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES]; + [self->occupantsArray sortUsingSelector:@selector(compare:)]; + result = [[NSArray alloc] initWithArray:self->occupantsArray copyItems:YES]; }}); return result; @@ -587,7 +587,7 @@ - (void)handlePresence:(XMPPPresence *)presence room:(XMPPRoom *)room if ([[presence type] isEqualToString:@"unavailable"]) { - XMPPRoomOccupantMemoryStorageObject *occupant = [occupantsDict objectForKey:from]; + XMPPRoomOccupantMemoryStorageObject *occupant = occupantsDict[from]; if (occupant) { // Occupant did leave - remove @@ -610,7 +610,7 @@ - (void)handlePresence:(XMPPPresence *)presence room:(XMPPRoom *)room } else { - XMPPRoomOccupantMemoryStorageObject *occupant = [occupantsDict objectForKey:from]; + XMPPRoomOccupantMemoryStorageObject *occupant = occupantsDict[from]; if (occupant == nil) { // Occupant did join - add @@ -618,7 +618,7 @@ - (void)handlePresence:(XMPPPresence *)presence room:(XMPPRoom *)room occupant = [[self.occupantClass alloc] initWithPresence:presence]; NSUInteger index = [self insertOccupant:occupant]; - [occupantsDict setObject:occupant forKey:from]; + occupantsDict[from] = occupant; // Notify delegate(s) diff --git a/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.h b/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.h index 2b6e655ca6..fc83094813 100644 --- a/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.h +++ b/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.h @@ -2,7 +2,7 @@ #import "XMPPRoomMessage.h" -@interface XMPPRoomMessageMemoryStorageObject : NSObject +@interface XMPPRoomMessageMemoryStorageObject : NSObject - (id)initWithIncomingMessage:(XMPPMessage *)message; - (id)initWithOutgoingMessage:(XMPPMessage *)message jid:(XMPPJID *)myRoomJID; diff --git a/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.m b/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.m index 1450bf37c8..02081fd74a 100644 --- a/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.m +++ b/Extensions/XEP-0045/MemoryStorage/XMPPRoomMessageMemoryStorageObject.m @@ -63,11 +63,24 @@ - (id)initWithCoder:(NSCoder *)coder { if ([coder allowsKeyedCoding]) { - message = [coder decodeObjectForKey:@"message"]; - jid = [coder decodeObjectForKey:@"jid"]; - localTimestamp = [coder decodeObjectForKey:@"localTimestamp"]; - remoteTimestamp = [coder decodeObjectForKey:@"remoteTimestamp"]; - isFromMe = [coder decodeBoolForKey:@"isFromMe"]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + message = [coder decodeObjectOfClass:[XMPPMessage class] forKey:@"message"]; + jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"]; + localTimestamp = [coder decodeObjectOfClass:[NSDate class] forKey:@"localTimestamp"]; + remoteTimestamp = [coder decodeObjectOfClass:[NSDate class] forKey:@"remoteTimestamp"]; + isFromMe = [coder decodeBoolForKey:@"isFromMe"]; + } + else + { + message = [coder decodeObjectForKey:@"message"]; + jid = [coder decodeObjectForKey:@"jid"]; + localTimestamp = [coder decodeObjectForKey:@"localTimestamp"]; + remoteTimestamp = [coder decodeObjectForKey:@"remoteTimestamp"]; + isFromMe = [coder decodeBoolForKey:@"isFromMe"]; + } + } else { @@ -97,10 +110,15 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:jid]; [coder encodeObject:localTimestamp]; [coder encodeObject:remoteTimestamp]; - [coder encodeObject:[NSNumber numberWithBool:isFromMe]]; + [coder encodeObject:@(isFromMe)]; } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Copying //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.h b/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.h index 2a1b89da87..cb9c44a7e0 100644 --- a/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.h +++ b/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.h @@ -3,7 +3,7 @@ #import "XMPPRoomOccupant.h" -@interface XMPPRoomOccupantMemoryStorageObject : NSObject +@interface XMPPRoomOccupantMemoryStorageObject : NSObject - (id)initWithPresence:(XMPPPresence *)presence; - (void)updateWithPresence:(XMPPPresence *)presence; diff --git a/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.m b/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.m index 67613d1130..514852f106 100644 --- a/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.m +++ b/Extensions/XEP-0045/MemoryStorage/XMPPRoomOccupantMemoryStorageObject.m @@ -38,8 +38,17 @@ - (id)initWithCoder:(NSCoder *)coder { if ([coder allowsKeyedCoding]) { - presence = [coder decodeObjectForKey:@"presence"]; - jid = [coder decodeObjectForKey:@"jid"]; + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + presence = [coder decodeObjectOfClass:[XMPPPresence class] forKey:@"presence"]; + jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"]; + } + else + { + presence = [coder decodeObjectForKey:@"presence"]; + jid = [coder decodeObjectForKey:@"jid"]; + } } else { @@ -64,6 +73,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Copying //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Extensions/XEP-0045/XMPPMUC.h b/Extensions/XEP-0045/XMPPMUC.h index 53dac110b7..c3400b8849 100644 --- a/Extensions/XEP-0045/XMPPMUC.h +++ b/Extensions/XEP-0045/XMPPMUC.h @@ -19,6 +19,11 @@ * and provides an efficient query to see if a presence or message element is targeted at a room. * - It listens for MUC room invitations sent from other users. **/ +NS_ASSUME_NONNULL_BEGIN + +/** jabber:x:conference */ +extern NSString *const XMPPConferenceXmlns; + @interface XMPPMUC : XMPPModule { /* Inherited from XMPPModule: @@ -28,9 +33,9 @@ dispatch_queue_t moduleQueue; */ - NSMutableSet *rooms; + NSMutableSet *rooms; - XMPPIDTracker *xmppIDTracker; + XMPPIDTracker * _Nullable xmppIDTracker; } /* Inherited from XMPPModule: @@ -93,7 +98,7 @@ * * */ -- (void)xmppMUC:(XMPPMUC *)sender didDiscoverServices:(NSArray *)services; +- (void)xmppMUC:(XMPPMUC *)sender didDiscoverServices:(NSArray *)services; /** * Implement this method when calling [mucInstanse discoverServices]. It will be invoked if the request @@ -129,3 +134,4 @@ - (void)xmppMUC:(XMPPMUC *)sender failedToDiscoverRoomsForServiceNamed:(NSString *)serviceName withError:(NSError *)error; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0045/XMPPMUC.m b/Extensions/XEP-0045/XMPPMUC.m index b286394ffa..3dd2dc543d 100644 --- a/Extensions/XEP-0045/XMPPMUC.m +++ b/Extensions/XEP-0045/XMPPMUC.m @@ -11,6 +11,7 @@ NSString *const XMPPDiscoverItemsNamespace = @"http://jabber.org/protocol/disco#items"; NSString *const XMPPMUCErrorDomain = @"XMPPMUCErrorDomain"; +NSString *const XMPPConferenceXmlns = @"jabber:x:conference"; @interface XMPPMUC() { @@ -55,8 +56,8 @@ - (void)deactivate XMPPLogTrace(); dispatch_block_t block = ^{ @autoreleasepool { - [xmppIDTracker removeAllIDs]; - xmppIDTracker = nil; + [self->xmppIDTracker removeAllIDs]; + self->xmppIDTracker = nil; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -87,7 +88,7 @@ - (BOOL)isMUCRoomElement:(XMPPElement *)element dispatch_block_t block = ^{ @autoreleasepool { - result = [rooms containsObject:bareFrom]; + result = [self->rooms containsObject:bareFrom]; }}; @@ -128,23 +129,23 @@ - (void)discoverServices // This is a public method, so it may be invoked on any thread/queue. dispatch_block_t block = ^{ @autoreleasepool { - if (hasRequestedServices) return; // We've already requested services + if (self->hasRequestedServices) return; // We've already requested services - NSString *toStr = xmppStream.myJID.domain; + NSString *toStr = self->xmppStream.myJID.domain; NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPDiscoverItemsNamespace]; XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:[XMPPJID jidWithString:toStr] - elementID:[xmppStream generateUUID] + elementID:[self->xmppStream generateUUID] child:query]; - [xmppIDTracker addElement:iq + [self->xmppIDTracker addElement:iq target:self selector:@selector(handleDiscoverServicesQueryIQ:withInfo:) timeout:60]; - [xmppStream sendElement:iq]; - hasRequestedServices = YES; + [self->xmppStream sendElement:iq]; + self->hasRequestedServices = YES; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -175,24 +176,24 @@ - (BOOL)discoverRoomsForServiceNamed:(NSString *)serviceName return NO; dispatch_block_t block = ^{ @autoreleasepool { - if (hasRequestedRooms) return; // We've already requested rooms + if (self->hasRequestedRooms) return; // We've already requested rooms NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPDiscoverItemsNamespace]; XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:[XMPPJID jidWithString:serviceName] - elementID:[xmppStream generateUUID] + elementID:[self->xmppStream generateUUID] child:query]; - [xmppIDTracker addElement:iq + [self->xmppIDTracker addElement:iq target:self selector:@selector(handleDiscoverRoomsQueryIQ:withInfo:) timeout:60]; - [xmppStream sendElement:iq]; - hasRequestedRooms = YES; + [self->xmppStream sendElement:iq]; + self->hasRequestedRooms = YES; }}; - + if (dispatch_get_specific(moduleQueueTag)) block(); else @@ -221,7 +222,7 @@ - (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingIn withDefaultValue:0] userInfo:dict]; - [multicastDelegate xmppMUCFailedToDiscoverServices:self + [self->multicastDelegate xmppMUCFailedToDiscoverServices:self withError:error]; return; } @@ -230,8 +231,8 @@ - (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingIn xmlns:XMPPDiscoverItemsNamespace]; NSArray *items = [query elementsForName:@"item"]; - [multicastDelegate xmppMUC:self didDiscoverServices:items]; - hasRequestedServices = NO; // Set this back to NO to allow for future requests + [self->multicastDelegate xmppMUC:self didDiscoverServices:items]; + self->hasRequestedServices = NO; // Set this back to NO to allow for future requests }}; if (dispatch_get_specific(moduleQueueTag)) @@ -256,9 +257,9 @@ - (void)handleDiscoverRoomsQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo code:[errorElem attributeIntegerValueForName:@"code" withDefaultValue:0] userInfo:dict]; - [multicastDelegate xmppMUC:self -failedToDiscoverRoomsForServiceNamed:serviceName - withError:error]; + [self->multicastDelegate xmppMUC:self + failedToDiscoverRoomsForServiceNamed:serviceName + withError:error]; return; } @@ -266,10 +267,10 @@ - (void)handleDiscoverRoomsQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo xmlns:XMPPDiscoverItemsNamespace]; NSArray *items = [query elementsForName:@"item"]; - [multicastDelegate xmppMUC:self + [self->multicastDelegate xmppMUC:self didDiscoverRooms:items forServiceNamed:serviceName]; - hasRequestedRooms = NO; // Set this back to NO to allow for future requests + self->hasRequestedRooms = NO; // Set this back to NO to allow for future requests }}; if (dispatch_get_specific(moduleQueueTag)) @@ -308,7 +309,7 @@ - (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, moduleQueue, ^{ @autoreleasepool { - [rooms removeObject:roomJID]; + [self->rooms removeObject:roomJID]; }}); } } @@ -373,7 +374,7 @@ - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message NSXMLElement * invite = [x elementForName:@"invite"]; NSXMLElement * decline = [x elementForName:@"decline"]; - NSXMLElement * directInvite = [message elementForName:@"x" xmlns:@"jabber:x:conference"]; + NSXMLElement * directInvite = [message elementForName:@"x" xmlns:XMPPConferenceXmlns]; XMPPJID * roomJID = [message from]; diff --git a/Extensions/XEP-0045/XMPPMessage+XEP0045.h b/Extensions/XEP-0045/XMPPMessage+XEP0045.h index 039948cf1c..ff6bebb415 100644 --- a/Extensions/XEP-0045/XMPPMessage+XEP0045.h +++ b/Extensions/XEP-0045/XMPPMessage+XEP0045.h @@ -4,8 +4,8 @@ @interface XMPPMessage(XEP0045) -- (BOOL)isGroupChatMessage; -- (BOOL)isGroupChatMessageWithBody; -- (BOOL)isGroupChatMessageWithSubject; +@property (nonatomic, readonly) BOOL isGroupChatMessage; +@property (nonatomic, readonly) BOOL isGroupChatMessageWithBody; +@property (nonatomic, readonly) BOOL isGroupChatMessageWithSubject; @end diff --git a/Extensions/XEP-0045/XMPPRoom.h b/Extensions/XEP-0045/XMPPRoom.h index ae82229742..d6ac156aaf 100644 --- a/Extensions/XEP-0045/XMPPRoom.h +++ b/Extensions/XEP-0045/XMPPRoom.h @@ -9,6 +9,7 @@ @protocol XMPPRoomStorage; @protocol XMPPRoomDelegate; +NS_ASSUME_NONNULL_BEGIN static NSString *const XMPPMUCNamespace = @"http://jabber.org/protocol/muc"; static NSString *const XMPPMUCUserNamespace = @"http://jabber.org/protocol/muc#user"; static NSString *const XMPPMUCAdminNamespace = @"http://jabber.org/protocol/muc#admin"; @@ -29,19 +30,21 @@ static NSString *const XMPPMUCOwnerNamespace = @"http://jabber.org/protocol/muc#%20%20%20__strong%20XMPPJID%20*roomJID;%20-__strong%20XMPPJID%20*myRoomJID;-__strong%20NSString%20*myNickname;-__strong%20NSString%20*myOldNickname;+__strong%20XMPPJID%20*%20_Nullable%20myRoomJID;+__strong%20NSString%20*%20_Nullable%20myNickname;+__strong%20NSString%20*%20_Nullable%20myOldNickname;%20-__strong%20NSString%20*roomSubject;+__strong%20NSString%20*%20_Nullable%20roomSubject;%20-XMPPIDTracker%20*responseTracker;+XMPPIDTracker%20*%20_Nullable%20responseTracker;%20%20uint16_t%20state;%20}%20--%20(id)initWithRoomStorage:(id%20%3CXMPPRoomStorage%3E)storage%20jid:(XMPPJID%20*)roomJID;--%20(id)initWithRoomStorage:(id%20%3CXMPPRoomStorage%3E)storage%20jid:(XMPPJID%20*)roomJID%20dispatchQueue:(dispatch_queue_t)queue;+-%20(instancetype)%20init%20NS_UNAVAILABLE;+-%20(instancetype)initWithDispatchQueue:(nullable%20dispatch_queue_t)queue%20NS_UNAVAILABLE;+-%20(instancetype)initWithRoomStorage:(id%20%3CXMPPRoomStorage%3E)storage%20jid:(XMPPJID%20*)roomJID;+-%20(instancetype)initWithRoomStorage:(id%20%3CXMPPRoomStorage%3E)storage%20jid:(XMPPJID%20*)roomJID%20dispatchQueue:(nullable%20dispatch_queue_t)queue;%20%20/*%20Inherited%20from%20XMPPModule:%20@@%20-60,16%20+63,16%20@@%20static%20NSString%20*const%20XMPPMUCOwnerNamespace%20=%20@"http://jabber.org/protocol/muc# #pragma mark Properties -@property (readonly) id xmppRoomStorage; +@property (nonatomic, readonly) id xmppRoomStorage; -@property (readonly) XMPPJID * roomJID; // E.g. xmpp-development@conference.deusty.com +@property (nonatomic, readonly) XMPPJID * roomJID; // E.g. xmpp-development@conference.deusty.com -@property (readonly) XMPPJID * myRoomJID; // E.g. xmpp-development@conference.deusty.com/robbiehanson -@property (readonly) NSString * myNickname; // E.g. robbiehanson +@property (atomic, readonly, nullable) XMPPJID * myRoomJID; // E.g. xmpp-development@conference.deusty.com/robbiehanson +@property (atomic, readonly, nullable) NSString * myNickname; // E.g. robbiehanson -@property (readonly) NSString *roomSubject; +@property (atomic, readonly, nullable) NSString *roomSubject; -@property (readonly) BOOL isJoined; +@property (atomic, readonly) BOOL isJoined; #pragma mark Room Lifecycle @@ -97,8 +100,8 @@ static NSString *const XMPPMUCOwnerNamespace = @"http://jabber.org/protocol/muc#%20%20*%20@see%20fetchConfigurationForm%20%20*%20@see%20configureRoomUsingOptions:%20**/--%20(void)joinRoomUsingNickname:(NSString%20*)desiredNickname%20history:(NSXMLElement%20*)history;--%20(void)joinRoomUsingNickname:(NSString%20*)desiredNickname%20history:(NSXMLElement%20*)history%20password:(NSString%20*)passwd;+-%20(void)joinRoomUsingNickname:(NSString%20*)desiredNickname%20history:(nullable%20NSXMLElement%20*)history;+-%20(void)joinRoomUsingNickname:(NSString%20*)desiredNickname%20history:(nullable%20NSXMLElement%20*)history%20password:(nullable%20NSString%20*)passwd;%20%20/**%20%20*%20There%20are%20two%20ways%20to%20configure%20a%20room.@@%20-115,7%20+118,7%20@@%20static%20NSString%20*const%20XMPPMUCOwnerNamespace%20=%20@"http://jabber.org/protocol/muc# /** * Pass nil to accept the default configuration. **/ -- (void)configureRoomUsingOptions:(NSXMLElement *)roomConfigForm; +- (void)configureRoomUsingOptions:(nullable NSXMLElement *)roomConfigForm; - (void)leaveRoom; - (void)destroyRoom; @@ -125,7 +128,11 @@ static NSString *const XMPPMUCOwnerNamespace = @"http://jabber.org/protocol/muc#%20-%20(void)changeNickname:(NSString%20*)newNickname;%20-%20(void)changeRoomSubject:(NSString%20*)newRoomSubject;%20--%20(void)inviteUser:(XMPPJID%20*)jid%20withMessage:(NSString%20*)invitationMessage;+-%20(void)inviteUser:(XMPPJID%20*)jid%20withMessage:(nullable%20NSString%20*)invitationMessage;+/**+%20*%20Allows%20sending%20invitation%20message%20with%20multiple%20invitees+%20*/+-%20(void)inviteUsers:(NSArray%3CXMPPJID%20*%3E%20*)jids%20withMessage:(nullable%20NSString%20*)invitationMessage;%20%20-%20(void)sendMessage:(XMPPMessage%20*)message;%20@@%20-135,6%20+142,8%20@@%20static%20NSString%20*const%20XMPPMUCOwnerNamespace%20=%20@"http://jabber.org/protocol/muc# - (void)fetchBanList; - (void)fetchMembersList; +- (void)fetchAdminsList; +- (void)fetchOwnersList; - (void)fetchModeratorsList; /** @@ -157,10 +166,10 @@ static NSString *const XMPPMUCOwnerNamespace = @"http://jabber.org/protocol/muc#%20%20*%20@return%20The%20id%20of%20the%20XMPPIQ%20that%20was%20sent.%20%20*%20%20%20%20%20%20%20%20%20This%20may%20be%20used%20to%20match%20multiple%20change%20requests%20with%20the%20responses%20in%20xmppRoom:didEditPrivileges:.%20**/--%20(NSString%20*)editRoomPrivileges:(NSArray%20*)items;+-%20(NSString%20*)editRoomPrivileges:(NSArray%3CNSXMLElement*%3E%20*)items;%20-+%20(NSXMLElement%20*)itemWithAffiliation:(NSString%20*)affiliation%20jid:(XMPPJID%20*)jid;-+%20(NSXMLElement%20*)itemWithRole:(NSString%20*)role%20jid:(XMPPJID%20*)jid;++%20(NSXMLElement%20*)itemWithAffiliation:(nullable%20NSString%20*)affiliation%20jid:(nullable%20XMPPJID%20*)jid;++%20(NSXMLElement%20*)itemWithRole:(nullable%20NSString%20*)role%20jid:(nullable%20XMPPJID%20*)jid;%20%20@end%20@@%20-284,7%20+293,10%20@@%20static%20NSString%20*const%20XMPPMUCOwnerNamespace%20=%20@"http://jabber.org/protocol/muc# - (void)xmppRoomDidJoin:(XMPPRoom *)sender; - (void)xmppRoomDidLeave:(XMPPRoom *)sender; + - (void)xmppRoomDidDestroy:(XMPPRoom *)sender; +- (void)xmppRoom:(XMPPRoom *)sender didFailToDestroy:(XMPPIQ *)iqError; + - (void)xmppRoom:(XMPPRoom *)sender occupantDidJoin:(XMPPJID *)occupantJID withPresence:(XMPPPresence *)presence; - (void)xmppRoom:(XMPPRoom *)sender occupantDidLeave:(XMPPJID *)occupantJID withPresence:(XMPPPresence *)presence; @@ -302,10 +314,18 @@ static NSString *const XMPPMUCOwnerNamespace = @"http://jabber.org/protocol/muc#%20-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didFetchMembersList:(NSArray%20*)items;%20-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didNotFetchMembersList:(XMPPIQ%20*)iqError;%20+-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didFetchAdminsList:(NSArray%20*)items;+-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didNotFetchAdminsList:(XMPPIQ%20*)iqError;++-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didFetchOwnersList:(NSArray%20*)items;+-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didNotFetchOwnersList:(XMPPIQ%20*)iqError;+%20-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didFetchModeratorsList:(NSArray%20*)items;%20-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didNotFetchModeratorsList:(XMPPIQ%20*)iqError;%20%20-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didEditPrivileges:(XMPPIQ%20*)iqResult;%20-%20(void)xmppRoom:(XMPPRoom%20*)sender%20didNotEditPrivileges:(XMPPIQ%20*)iqError;+-%20(void)xmppRoomDidChangeSubject:(XMPPRoom%20*)sender;%20%20@end+NS_ASSUME_NONNULL_ENDdiff%20--git%20a/Extensions/XEP-0045/XMPPRoom.m%20b/Extensions/XEP-0045/XMPPRoom.mindex%2022379f3b57..68f0b173a0%20100644---%20a/Extensions/XEP-0045/XMPPRoom.m+++%20b/Extensions/XEP-0045/XMPPRoom.m@@%20-34,22%20+34,6%20@@%20@interface%20XMPPRoom%20()%20%20@implementation%20XMPPRoom%20--%20(id)init-{-//%20This%20will%20cause%20a%20crash%20-%20it's designed to. - // Only the init methods listed in XMPPRoom.h are supported. - - return [self initWithRoomStorage:nil jid:nil dispatchQueue:NULL]; -} - -- (id)initWithDispatchQueue:(dispatch_queue_t)queue -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPRoom.h are supported. - - return [self initWithRoomStorage:nil jid:nil dispatchQueue:queue]; -} - - (id)initWithRoomStorage:(id )storage jid:(XMPPJID *)aRoomJID { return [self initWithRoomStorage:storage jid:aRoomJID dispatchQueue:NULL]; @@ -99,8 +83,8 @@ - (void)deactivate [self leaveRoom]; } - [responseTracker removeAllIDs]; - responseTracker = nil; + [self->responseTracker removeAllIDs]; + self->responseTracker = nil; }}; @@ -159,7 +143,7 @@ - (XMPPJID *)myRoomJID __block XMPPJID *result; dispatch_sync(moduleQueue, ^{ - result = myRoomJID; + result = self->myRoomJID; }); return result; @@ -177,7 +161,7 @@ - (NSString *)myNickname __block NSString *result; dispatch_sync(moduleQueue, ^{ - result = myNickname; + result = self->myNickname; }); return result; @@ -195,7 +179,7 @@ - (NSString *)roomSubject __block NSString *result; dispatch_sync(moduleQueue, ^{ - result = roomSubject; + result = self->roomSubject; }); return result; @@ -207,7 +191,7 @@ - (BOOL)isJoined __block BOOL result = 0; dispatch_block_t block = ^{ - result = (state & kXMPPRoomStateJoined) ? YES : NO; + result = (self->state & kXMPPRoomStateJoined) ? YES : NO; }; if (dispatch_get_specific(moduleQueueTag)) @@ -266,7 +250,7 @@ - (void)joinRoomUsingNickname:(NSString *)desiredNickname history:(NSXMLElement { dispatch_block_t block = ^{ @autoreleasepool { - XMPPLogTrace2(@"%@[%@] - %@", THIS_FILE, roomJID, THIS_METHOD); + XMPPLogTrace2(@"%@[%@] - %@", THIS_FILE, self->roomJID, THIS_METHOD); // Check state and update variables @@ -292,12 +276,12 @@ - (void)joinRoomUsingNickname:(NSString *)desiredNickname history:(NSXMLElement [x addChild:[NSXMLElement elementWithName:@"password" stringValue:passwd]]; } - XMPPPresence *presence = [XMPPPresence presenceWithType:nil to:myRoomJID]; + XMPPPresence *presence = [XMPPPresence presenceWithType:nil to:self->myRoomJID]; [presence addChild:x]; - [xmppStream sendElement:presence]; + [self->xmppStream sendElement:presence]; - state |= kXMPPRoomStateJoining; + self->state |= kXMPPRoomStateJoining; }}; @@ -359,14 +343,14 @@ - (void)fetchConfigurationForm // // - NSString *fetchID = [xmppStream generateUUID]; + NSString *fetchID = [self->xmppStream generateUUID]; NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->roomJID elementID:fetchID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:fetchID + [self->responseTracker addID:fetchID target:self selector:@selector(handleConfigurationFormResponse:withInfo:) timeout:60.0]; @@ -428,13 +412,13 @@ - (void)configureRoomUsingOptions:(NSXMLElement *)roomConfigForm NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace]; [query addChild:x]; - NSString *iqID = [xmppStream generateUUID]; + NSString *iqID = [self->xmppStream generateUUID]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->roomJID elementID:iqID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:iqID + [self->responseTracker addID:iqID target:self selector:@selector(handleConfigureRoomResponse:withInfo:) timeout:60.0]; @@ -458,13 +442,13 @@ - (void)configureRoomUsingOptions:(NSXMLElement *)roomConfigForm NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace]; [query addChild:x]; - NSString *iqID = [xmppStream generateUUID]; + NSString *iqID = [self->xmppStream generateUUID]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->roomJID elementID:iqID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:iqID + [self->responseTracker addID:iqID target:self selector:@selector(handleConfigureRoomResponse:withInfo:) timeout:60.0]; @@ -488,7 +472,13 @@ - (void)changeNickname:(NSString *)newNickname - (void)changeRoomSubject:(NSString *)newRoomSubject { - // Todo + NSXMLElement *subject = [NSXMLElement elementWithName:@"subject" stringValue:newRoomSubject]; + + XMPPMessage *message = [XMPPMessage message]; + [message addAttributeWithName:@"from" stringValue:[xmppStream.myJID full]]; + [message addChild:subject]; + + [self sendMessage:message]; } - (void)handleFetchBanListResponse:(XMPPIQ *)iq withInfo:(id )info @@ -530,7 +520,7 @@ - (void)fetchBanList // // - NSString *fetchID = [xmppStream generateUUID]; + NSString *fetchID = [self->xmppStream generateUUID]; NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; [item addAttributeWithName:@"affiliation" stringValue:@"outcast"]; @@ -538,11 +528,11 @@ - (void)fetchBanList NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace]; [query addChild:item]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->roomJID elementID:fetchID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:fetchID + [self->responseTracker addID:fetchID target:self selector:@selector(handleFetchBanListResponse:withInfo:) timeout:60.0]; @@ -591,7 +581,7 @@ - (void)fetchMembersList // // - NSString *fetchID = [xmppStream generateUUID]; + NSString *fetchID = [self->xmppStream generateUUID]; NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; [item addAttributeWithName:@"affiliation" stringValue:@"member"]; @@ -599,11 +589,11 @@ - (void)fetchMembersList NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace]; [query addChild:item]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->roomJID elementID:fetchID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:fetchID + [self->responseTracker addID:fetchID target:self selector:@selector(handleFetchMembersListResponse:withInfo:) timeout:60.0]; @@ -617,6 +607,130 @@ - (void)fetchMembersList } +- (void)handleFetchAdminsListResponse:(XMPPIQ *)iq withInfo:(id )info +{ + if ([[iq type] isEqualToString:@"result"]) + { + // + // + // + // + // + + NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPMUCAdminNamespace]; + NSArray *items = [query elementsForName:@"item"]; + + [multicastDelegate xmppRoom:self didFetchAdminsList:items]; + } + else + { + [multicastDelegate xmppRoom:self didNotFetchAdminsList:iq]; + } +} + +- (void)fetchAdminsList +{ + dispatch_block_t block = ^{ @autoreleasepool { + + XMPPLogTrace(); + + // + // + // + // + // + + NSString *fetchID = [self->xmppStream generateUUID]; + + NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; + [item addAttributeWithName:@"affiliation" stringValue:@"admin"]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace]; + [query addChild:item]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->roomJID elementID:fetchID child:query]; + + [self->xmppStream sendElement:iq]; + + [self->responseTracker addID:fetchID + target:self + selector:@selector(handleFetchAdminsListResponse:withInfo:) + timeout:60.0]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); + + +} + +- (void)handleFetchOwnersListResponse:(XMPPIQ *)iq withInfo:(id )info +{ + if ([[iq type] isEqualToString:@"result"]) + { + // + // + // + // + // + + NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPMUCAdminNamespace]; + NSArray *items = [query elementsForName:@"item"]; + + [multicastDelegate xmppRoom:self didFetchOwnersList:items]; + } + else + { + [multicastDelegate xmppRoom:self didNotFetchOwnersList:iq]; + } +} + +- (void)fetchOwnersList +{ + dispatch_block_t block = ^{ @autoreleasepool { + + XMPPLogTrace(); + + // + // + // + // + // + + NSString *fetchID = [self->xmppStream generateUUID]; + + NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; + [item addAttributeWithName:@"affiliation" stringValue:@"owner"]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace]; + [query addChild:item]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->roomJID elementID:fetchID child:query]; + + [self->xmppStream sendElement:iq]; + + [self->responseTracker addID:fetchID + target:self + selector:@selector(handleFetchOwnersListResponse:withInfo:) + timeout:60.0]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + - (void)handleFetchModeratorsListResponse:(XMPPIQ *)iq withInfo:(id )info { if ([[iq type] isEqualToString:@"result"]) @@ -652,7 +766,7 @@ - (void)fetchModeratorsList // // - NSString *fetchID = [xmppStream generateUUID]; + NSString *fetchID = [self->xmppStream generateUUID]; NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; [item addAttributeWithName:@"role" stringValue:@"moderator"]; @@ -660,11 +774,11 @@ - (void)fetchModeratorsList NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace]; [query addChild:item]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->roomJID elementID:fetchID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:fetchID + [self->responseTracker addID:fetchID target:self selector:@selector(handleFetchModeratorsListResponse:withInfo:) timeout:60.0]; @@ -712,11 +826,11 @@ - (NSString *)editRoomPrivileges:(NSArray *)items [query addChild:item]; } - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->roomJID elementID:iqID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:iqID + [self->responseTracker addID:iqID target:self selector:@selector(handleEditRoomPrivilegesResponse:withInfo:) timeout:60.0]; @@ -744,14 +858,14 @@ - (void)leaveRoom // XMPPPresence *presence = [XMPPPresence presence]; - [presence addAttributeWithName:@"to" stringValue:[myRoomJID full]]; + [presence addAttributeWithName:@"to" stringValue:[self->myRoomJID full]]; [presence addAttributeWithName:@"type" stringValue:@"unavailable"]; - [xmppStream sendElement:presence]; + [self->xmppStream sendElement:presence]; - state &= ~kXMPPRoomStateJoining; - state &= ~kXMPPRoomStateJoined; - state |= kXMPPRoomStateLeaving; + self->state &= ~kXMPPRoomStateJoining; + self->state &= ~kXMPPRoomStateJoined; + self->state |= kXMPPRoomStateLeaving; }}; @@ -765,13 +879,13 @@ - (void)handleDestroyRoomResponse:(XMPPIQ *)iq withInfo:(id )i { XMPPLogTrace(); - if ([[iq type] isEqualToString:@"result"]) + if ([iq isResultIQ]) { [multicastDelegate xmppRoomDidDestroy:self]; } else { - // Todo... + [multicastDelegate xmppRoom:self didFailToDestroy:iq]; } } @@ -792,13 +906,13 @@ - (void)destroyRoom NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace]; [query addChild:destroy]; - NSString *iqID = [xmppStream generateUUID]; + NSString *iqID = [self->xmppStream generateUUID]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->roomJID elementID:iqID child:query]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; - [responseTracker addID:iqID + [self->responseTracker addID:iqID target:self selector:@selector(handleDestroyRoomResponse:withInfo:) timeout:60.0]; @@ -815,45 +929,61 @@ - (void)destroyRoom #pragma mark Messages //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)inviteUser:(XMPPJID *)jid withMessage:(NSString *)inviteMessageStr +- (void)inviteUser:(XMPPJID *)jid withMessage:(NSString *)invitationMessage { - dispatch_block_t block = ^{ @autoreleasepool { - - XMPPLogTrace(); - - // - // - // - // - // Hey Hecate, this is the place for all good witches! - // - // - // - // - - NSXMLElement *invite = [NSXMLElement elementWithName:@"invite"]; - [invite addAttributeWithName:@"to" stringValue:[jid full]]; - - if ([inviteMessageStr length] > 0) - { - [invite addChild:[NSXMLElement elementWithName:@"reason" stringValue:inviteMessageStr]]; - } - - NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:XMPPMUCUserNamespace]; - [x addChild:invite]; - - XMPPMessage *message = [XMPPMessage message]; - [message addAttributeWithName:@"to" stringValue:[roomJID full]]; - [message addChild:x]; - - [xmppStream sendElement:message]; - - }}; - - if (dispatch_get_specific(moduleQueueTag)) - block(); - else - dispatch_async(moduleQueue, block); + [self inviteUsers:@[jid] withMessage:invitationMessage]; +} + +- (void)inviteUsers:(NSArray *)jids withMessage:(NSString *)invitationMessage +{ + dispatch_block_t block = ^{ @autoreleasepool { + + XMPPLogTrace(); + + // + // + // + // + // Invitation message + // + // + // '> + // + // Invitation message + // + // + // + // + + NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:XMPPMUCUserNamespace]; + + for (XMPPJID *jid in jids) { + NSXMLElement *invite = [self inviteElementWithJid:jid invitationMessage:invitationMessage]; + [x addChild:invite]; + } + + XMPPMessage *message = [XMPPMessage message]; + [message addAttributeWithName:@"to" stringValue:[self->roomJID full]]; + [message addChild:x]; + + [self->xmppStream sendElement:message]; + + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (NSXMLElement *)inviteElementWithJid:(XMPPJID *)jid invitationMessage:(NSString *)invitationMessage { + NSXMLElement *invite = [NSXMLElement elementWithName:@"invite"]; + [invite addAttributeWithName:@"to" stringValue:[jid full]]; + + if ([invitationMessage length] > 0) { + [invite addChild:[NSXMLElement elementWithName:@"reason" stringValue:invitationMessage]]; + } + return invite; } - (void)sendMessage:(XMPPMessage *)message @@ -862,10 +992,10 @@ - (void)sendMessage:(XMPPMessage *)message XMPPLogTrace(); - [message addAttributeWithName:@"to" stringValue:[roomJID full]]; + [message addAttributeWithName:@"to" stringValue:[self->roomJID full]]; [message addAttributeWithName:@"type" stringValue:@"groupchat"]; - [xmppStream sendElement:message]; + [self->xmppStream sendElement:message]; }}; @@ -1084,6 +1214,7 @@ - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message else if ([message isGroupChatMessageWithSubject]) { roomSubject = [message subject]; + [multicastDelegate xmppRoomDidChangeSubject:self]; } else { diff --git a/Extensions/XEP-0045/XMPPRoomMessage.h b/Extensions/XEP-0045/XMPPRoomMessage.h index 6b395eaf61..9129ece063 100644 --- a/Extensions/XEP-0045/XMPPRoomMessage.h +++ b/Extensions/XEP-0045/XMPPRoomMessage.h @@ -3,35 +3,35 @@ @class XMPPJID; @class XMPPMessage; - +NS_ASSUME_NONNULL_BEGIN @protocol XMPPRoomMessage /** * The raw message that was sent / received. **/ -- (XMPPMessage *)message; +@property (nonatomic, readonly) XMPPMessage *message; /** * The JID of the MUC room. **/ -- (XMPPJID *)roomJID; +@property (nonatomic, readonly) XMPPJID *roomJID; /** * Who sent the message. * A typical MUC room jid is of the form "room_name@conference.domain.tld/some_nickname". **/ -- (XMPPJID *)jid; +@property (nonatomic, readonly) XMPPJID *jid; /** * The nickname of the user who sent the message. * This is a convenience method for [jid resource]. **/ -- (NSString *)nickname; +@property (nonatomic, readonly) NSString *nickname; /** * Convenience method to access the body of the message. **/ -- (NSString *)body; +@property (nonatomic, readonly) NSString *body; /** * When the message was sent / received (as recorded by us). @@ -41,18 +41,19 @@ * This is the case when first joining a room, and downloading the discussion history. * In such a case, the localTimestamp will be a reflection of the serverTimestamp. **/ -- (NSDate *)localTimestamp; +@property (nonatomic, readonly) NSDate *localTimestamp; /** * When the message was sent / received (as recorded by the server). * * Only set when the server includes a delayedDelivery timestamp within the message. **/ -- (NSDate *)remoteTimestamp; +@property (nonatomic, readonly, nullable) NSDate *remoteTimestamp; /** * Whether or not the message was sent by us. **/ -- (BOOL)isFromMe; +@property (nonatomic, readonly) BOOL isFromMe; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0045/XMPPRoomOccupant.h b/Extensions/XEP-0045/XMPPRoomOccupant.h index 1e7e77b9d9..2f1eb9f8e2 100644 --- a/Extensions/XEP-0045/XMPPRoomOccupant.h +++ b/Extensions/XEP-0045/XMPPRoomOccupant.h @@ -3,30 +3,30 @@ @class XMPPJID; @class XMPPPresence; - +NS_ASSUME_NONNULL_BEGIN @protocol XMPPRoomOccupant /** * Most recent presence message from occupant. **/ -- (XMPPPresence *)presence; +@property (nonatomic, readonly) XMPPPresence *presence; /** * The MUC room the occupant is associated with. **/ -- (XMPPJID *)roomJID; +@property (nonatomic, readonly) XMPPJID *roomJID; /** * The JID of the occupant as reported by the room. * A typical MUC room will use JIDs of the form: "room_name@conference.domain.tl/some_nickname". **/ -- (XMPPJID *)jid; +@property (nonatomic, readonly) XMPPJID *jid; /** * The nickname of the user. * In other words, the resource portion of the occupants JID. **/ -- (NSString *)nickname; +@property (nonatomic, readonly) NSString *nickname; /** * The 'role' and 'affiliation' of the occupant within the MUC room. @@ -43,8 +43,8 @@ * * For more information, please see XEP-0045. **/ -- (NSString *)role; -- (NSString *)affiliation; +@property (nonatomic, readonly, nullable) NSString *role; +@property (nonatomic, readonly, nullable) NSString *affiliation; /** * If the MUC room is non-anonymous, the real JID of the user will be broadcast. @@ -52,6 +52,7 @@ * An anonymous room uses JID's of the form: "room_name@conference.domain.tld/some_nickname". * A non-anonymous room also includes the occupants real full JID in the presence broadcast. **/ -- (XMPPJID *)realJID; +@property (nonatomic, readonly, nullable) XMPPJID *realJID; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0045/XMPPRoomPrivate.h b/Extensions/XEP-0045/XMPPRoomPrivate.h index 0240f93708..6adfe07368 100644 --- a/Extensions/XEP-0045/XMPPRoomPrivate.h +++ b/Extensions/XEP-0045/XMPPRoomPrivate.h @@ -1,6 +1,6 @@ #import "XMPPRoom.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPRoom (PrivateInternalAPI) /** @@ -12,8 +12,9 @@ * The parent's dispatch queue is passed in the configureWithParent:queue: method, * or may be obtained via the moduleQueue method below. **/ -- (GCDMulticastDelegate *)multicastDelegate; +@property (nonatomic, readonly) GCDMulticastDelegate *multicastDelegate; -- (dispatch_queue_t)moduleQueue; +@property (nonatomic, readonly) dispatch_queue_t moduleQueue; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0048/NSXMLElement+XEP_0048.h b/Extensions/XEP-0048/NSXMLElement+XEP_0048.h new file mode 100644 index 0000000000..3ddedde7c8 --- /dev/null +++ b/Extensions/XEP-0048/NSXMLElement+XEP_0048.h @@ -0,0 +1,67 @@ +// +// NSXMLElement+XEP_0048.h +// XMPPFramework +// +// Created by Chris Ballinger on 11/10/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import +#import "XMPPJID.h" +#import "XMPPElement.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPConferenceBookmark, XMPPURLBookmark, XMPPBookmarksStorageElement; +@protocol XMPPBookmark; + +/** + * XEP-0048: Bookmarks + * + * This specification defines an XML data format for use by XMPP clients in storing bookmarks to mult-user chatrooms and web pages. The chatroom bookmarking function includes the ability to auto-join rooms on login. + * https://xmpp.org/extensions/xep-0048.html + */ +@interface NSXMLElement (XEP_0048) + +/** Extracts child element of type */ +@property (nonatomic, readonly, nullable) XMPPBookmarksStorageElement *bookmarksStorageElement; + +@end + + + + +/** XML Constants */ +@interface XMPPBookmarkConstants : NSObject + +/** "storage:bookmarks" */ +@property (nonatomic, class, readonly) NSString *xmlns; + +// MARK: Elements + +/** "storage" */ +@property (nonatomic, class, readonly) NSString *storageElement; +/** "conference" */ +@property (nonatomic, class, readonly) NSString *conferenceElement; +/** "url" */ +@property (nonatomic, class, readonly) NSString *urlElement; +/** "nick" */ +@property (nonatomic, class, readonly) NSString *nickElement; +/** "password" */ +@property (nonatomic, class, readonly) NSString *passwordElement; + +// MARK: Attributes + +/** "name" */ +@property (nonatomic, class, readonly) NSString *nameAttribute; +/** "autojoin" */ +@property (nonatomic, class, readonly) NSString *autojoinAttribute; +/** "jid" */ +@property (nonatomic, class, readonly) NSString *jidAttribute; +/** "url" */ +@property (nonatomic, class, readonly) NSString *urlAttribute; + +- (instancetype) init NS_UNAVAILABLE; +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0048/NSXMLElement+XEP_0048.m b/Extensions/XEP-0048/NSXMLElement+XEP_0048.m new file mode 100644 index 0000000000..0e3146a3f1 --- /dev/null +++ b/Extensions/XEP-0048/NSXMLElement+XEP_0048.m @@ -0,0 +1,53 @@ +// +// NSXMLElement+XEP_0048.m +// XMPPFramework +// +// Created by Chris Ballinger on 11/10/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "NSXMLElement+XEP_0048.h" +#import "NSXMLElement+XMPP.h" +#import "XMPPBookmarksStorageElement.h" + + +@implementation NSXMLElement (XEP_0048) + +- (XMPPBookmarksStorageElement*) bookmarksStorageElement { + NSXMLElement *element = [self elementForName:XMPPBookmarkConstants.storageElement xmlns:XMPPBookmarkConstants.xmlns]; + return [XMPPBookmarksStorageElement bookmarksStorageElementFromElement:element]; +} +@end + +@implementation XMPPBookmarkConstants ++ (NSString*) xmlns { + return @"storage:bookmarks"; +} ++ (NSString*) storageElement { + return @"storage"; +} ++ (NSString*) conferenceElement { + return @"conference"; +} ++ (NSString*) urlElement { + return @"url"; +} ++ (NSString*) urlAttribute { + return @"url"; +} ++ (NSString*) passwordElement { + return @"password"; +} ++ (NSString*) nickElement { + return @"nick"; +} ++ (NSString*) nameAttribute { + return @"name"; +} ++ (NSString*) autojoinAttribute { + return @"autojoin"; +} ++ (NSString*) jidAttribute { + return @"jid"; +} +@end diff --git a/Extensions/XEP-0048/XMPPBookmark.h b/Extensions/XEP-0048/XMPPBookmark.h new file mode 100644 index 0000000000..4b6221621d --- /dev/null +++ b/Extensions/XEP-0048/XMPPBookmark.h @@ -0,0 +1,59 @@ +// +// XMPPBookmark.h +// XMPPFramework +// +// Created by Chris Ballinger on 11/12/17. +// Copyright © 2017 robbiehanson. All rights reserved. +// + +#import +#import "XMPPJID.h" +#import "XMPPElement.h" + +NS_ASSUME_NONNULL_BEGIN +@protocol XMPPBookmark +@required +/** A friendly name for the bookmark. */ +@property (nonatomic, copy, readonly, nullable) NSString *bookmarkName; +/** NSXMLElement representation, either or element */ +@property (nonatomic, readonly) NSXMLElement *element; +/** Converts element in place. Must be or element */ ++ (nullable instancetype) bookmarkFromElement:(NSXMLElement*)element; +/** Element name, either "conference" or "url" */ +@property (nonatomic, class, readonly) NSString *elementName; +@end + +@interface XMPPConferenceBookmark : NSXMLElement +/** The JabberID of the chat room. */ +@property (nonatomic, strong, readonly, nullable) XMPPJID *jid; +/** Whether the client should automatically join the conference room on login. */ +@property (nonatomic, readonly) BOOL autoJoin; +/** The user's preferred roomnick for the chatroom. */ +@property (nonatomic, copy, readonly, nullable) NSString *nick; +/** ⚠️ Unencrypted string for the password needed to enter a password-protected room. For security reasons, use of this element is NOT RECOMMENDED. */ +@property (nonatomic, copy, readonly, nullable) NSString *password; + +- (instancetype) initWithJID:(XMPPJID*)jid; +- (instancetype) initWithJID:(XMPPJID*)jid + bookmarkName:(nullable NSString*)name + nick:(nullable NSString*)bookmarkName + autoJoin:(BOOL)autoJoin; + +/** Using a password is NOT RECOMMENDED because it is stored on the server unencrypted. */ +- (instancetype) initWithJID:(XMPPJID*)jid + bookmarkName:(nullable NSString*)bookmarkName + nick:(nullable NSString*)nick + autoJoin:(BOOL)autoJoin + password:(nullable NSString*)password; + +@end + +@interface XMPPURLBookmark: NSXMLElement +/** The HTTP or HTTPS URL of the web page. */ +@property (nonatomic, copy, readonly, nullable) NSURL *url; + +- (instancetype) initWithURL:(NSURL*)url; +- (instancetype) initWithURL:(NSURL*)url + bookmarkName:(nullable NSString*)bookmarkName; +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0048/XMPPBookmark.m b/Extensions/XEP-0048/XMPPBookmark.m new file mode 100644 index 0000000000..3b27f7618c --- /dev/null +++ b/Extensions/XEP-0048/XMPPBookmark.m @@ -0,0 +1,149 @@ +// +// XMPPBookmark.m +// XMPPFramework +// +// Created by Chris Ballinger on 11/12/17. +// Copyright © 2017 robbiehanson. All rights reserved. +// + +#import "XMPPBookmark.h" +#import "NSXMLElement+XEP_0048.h" +#import "NSXMLElement+XMPP.h" +#import + +@implementation XMPPConferenceBookmark + +// MARK: Init + +- (instancetype) initWithJID:(XMPPJID*)jid { + return [self initWithJID:jid bookmarkName:nil nick:nil autoJoin:NO password:nil]; +} + +- (instancetype) initWithJID:(XMPPJID*)jid + bookmarkName:(nullable NSString*)bookmarkName + nick:(nullable NSString*)nick + autoJoin:(BOOL)autoJoin { + return [self initWithJID:jid bookmarkName:bookmarkName nick:nick autoJoin:autoJoin password:nil]; +} + +- (instancetype) initWithJID:(XMPPJID*)jid + bookmarkName:(nullable NSString*)bookmarkName + nick:(nullable NSString*)nick + autoJoin:(BOOL)autoJoin + password:(nullable NSString*)password { + if (self = [super initWithName:self.class.elementName]) { + [self addAttributeWithName:XMPPBookmarkConstants.jidAttribute stringValue:jid.bare]; + + if (bookmarkName.length) { + [self addAttributeWithName:XMPPBookmarkConstants.nameAttribute stringValue:bookmarkName]; + } + [self addAttributeWithName:XMPPBookmarkConstants.autojoinAttribute boolValue:autoJoin]; + + if (nick.length) { + NSXMLElement *nickElement = [NSXMLElement elementWithName:XMPPBookmarkConstants.nickElement stringValue:nick]; + [self addChild:nickElement]; + } + if (password.length) { + NSXMLElement *passwordElement = [NSXMLElement elementWithName:XMPPBookmarkConstants.passwordElement stringValue:password]; + [self addChild:passwordElement]; + } + } + return self; +} + +// MARK: Properties + +- (XMPPJID*) jid { + NSString *jidString = [self attributeStringValueForName:XMPPBookmarkConstants.jidAttribute]; + if (!jidString) { return nil; } + return [XMPPJID jidWithString:jidString]; +} + +- (BOOL) autoJoin { + return [self attributeBoolValueForName:XMPPBookmarkConstants.autojoinAttribute withDefaultValue:NO]; +} + +- (NSString*) nick { + return [self elementForName:XMPPBookmarkConstants.nickElement].stringValue; +} + +- (NSString*) password { + return [self elementForName:XMPPBookmarkConstants.passwordElement].stringValue; +} + +// MARK: XMPPBookmark + ++ (nullable instancetype) bookmarkFromElement:(NSXMLElement*)element { + if (![element.name isEqualToString:self.class.elementName]) { + return nil; + } + object_setClass(element, self.class); + return (id)element; +} + +- (NSString*) bookmarkName { + return [self attributeStringValueForName:XMPPBookmarkConstants.nameAttribute]; +} + ++ (NSString*) elementName { + return XMPPBookmarkConstants.conferenceElement; +} + +- (NSXMLElement*) element { + return self; +} + +@end + +@implementation XMPPURLBookmark + +// MARK: Init + +- (instancetype) initWithURL:(NSURL*)url { + return [self initWithURL:url bookmarkName:nil]; +} + +- (instancetype) initWithURL:(NSURL*)url + bookmarkName:(nullable NSString*)bookmarkName { + if (self = [super initWithName:self.class.elementName]) { + if (bookmarkName.length) { + [self addAttributeWithName:XMPPBookmarkConstants.nameAttribute stringValue:bookmarkName]; + } + [self addAttributeWithName:XMPPBookmarkConstants.urlAttribute stringValue:url.absoluteString]; + } + return self; +} + +// MARK: Properties + +- (NSURL*) url { + NSString *urlString = [self attributeStringValueForName:XMPPBookmarkConstants.urlAttribute]; + if (!urlString) { return nil; } + NSURL *url = [NSURL URLWithString:urlString]; + return url; +} + +// MARK: XMPPBookmark + ++ (nullable instancetype) bookmarkFromElement:(NSXMLElement*)element { + if (![element.name isEqualToString:self.class.elementName]) { + return nil; + } + object_setClass(element, self.class); + return (id)element; +} + +- (NSString*) bookmarkName { + return [self attributeStringValueForName:XMPPBookmarkConstants.nameAttribute]; +} + ++ (NSString*) elementName { + return XMPPBookmarkConstants.urlElement; +} + +- (NSXMLElement*) element { + return self; +} + +@end + diff --git a/Extensions/XEP-0048/XMPPBookmarksStorageElement.h b/Extensions/XEP-0048/XMPPBookmarksStorageElement.h new file mode 100644 index 0000000000..5f6c9728a4 --- /dev/null +++ b/Extensions/XEP-0048/XMPPBookmarksStorageElement.h @@ -0,0 +1,27 @@ +// +// XMPPBookmarksStorageElement.h +// XMPPFramework +// +// Created by Chris Ballinger on 11/12/17. +// Copyright © 2017 robbiehanson. All rights reserved. +// + +#import +#import "XMPPBookmark.h" + +NS_ASSUME_NONNULL_BEGIN +/** */ +@interface XMPPBookmarksStorageElement : NSXMLElement + +/** Converts element in place */ ++ (nullable XMPPBookmarksStorageElement*)bookmarksStorageElementFromElement:(NSXMLElement*)element; + +/** Create new element from bookmarks */ +- (instancetype) initWithBookmarks:(NSArray>*)bookmarks; + +@property (nonatomic, strong, readonly) NSArray> *bookmarks; +@property (nonatomic, strong, readonly) NSArray *conferenceBookmarks; +@property (nonatomic, strong, readonly) NSArray *urlBookmarks; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0048/XMPPBookmarksStorageElement.m b/Extensions/XEP-0048/XMPPBookmarksStorageElement.m new file mode 100644 index 0000000000..b57e2f86ea --- /dev/null +++ b/Extensions/XEP-0048/XMPPBookmarksStorageElement.m @@ -0,0 +1,69 @@ +// +// XMPPBookmarksStorageElement.m +// XMPPFramework +// +// Created by Chris Ballinger on 11/12/17. +// Copyright © 2017 robbiehanson. All rights reserved. +// + +#import "XMPPBookmarksStorageElement.h" +#import "NSXMLElement+XEP_0048.h" +#import "NSXMLElement+XMPP.h" +#import "XMPPBookmark.h" +#import + +#define let __auto_type const + +@implementation XMPPBookmarksStorageElement + ++ (nullable XMPPBookmarksStorageElement*)bookmarksStorageElementFromElement:(NSXMLElement*)element { + NSParameterAssert(element); + if (!element || + ![element.name isEqualToString:XMPPBookmarkConstants.storageElement] || + ![element.xmlns isEqualToString:XMPPBookmarkConstants.xmlns]) { + return nil; + } + object_setClass(element, XMPPBookmarksStorageElement.class); + return (XMPPBookmarksStorageElement*)element; +} + +- (instancetype) initWithBookmarks:(NSArray>*)bookmarks { + if (self = [super initWithName:XMPPBookmarkConstants.storageElement xmlns:XMPPBookmarkConstants.xmlns]) { + [bookmarks enumerateObjectsUsingBlock:^(id _Nonnull bookmark, NSUInteger idx, BOOL * _Nonnull stop) { + [self addChild:bookmark.element]; + }]; + } + return self; +} + +- (NSArray>*)bookmarks { + let conferences = self.conferenceBookmarks; + let urls = self.urlBookmarks; + NSMutableArray> *bookmarks = [NSMutableArray arrayWithCapacity:conferences.count + urls.count]; + [bookmarks addObjectsFromArray:conferences]; + [bookmarks addObjectsFromArray:urls]; + return bookmarks; +} + + +- (NSArray<__kindof id>*)bookmarksWithElementName:(NSString*)elementName class:(Class)class { + NSArray *bookmarkElements = [self elementsForName:elementName]; + NSMutableArray> *bookmarks = [[NSMutableArray alloc] initWithCapacity:bookmarkElements.count]; + [bookmarkElements enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull bookmarkElement, NSUInteger idx, BOOL * _Nonnull stop) { + id bookmark = [class bookmarkFromElement:bookmarkElement]; + if (bookmark) { + [bookmarks addObject:bookmark]; + } + }]; + return bookmarks; +} + +- (NSArray*)conferenceBookmarks { + return [self bookmarksWithElementName:XMPPConferenceBookmark.elementName class:XMPPConferenceBookmark.class]; +} + +- (NSArray*)urlBookmarks { + return [self bookmarksWithElementName:XMPPURLBookmark.elementName class:XMPPURLBookmark.class]; +} + +@end diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/.xccurrentversion b/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/.xccurrentversion index f9bae00067..9351e43d81 100644 --- a/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/.xccurrentversion +++ b/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - XMPPvCard.xcdatamodel + XMPPvCard2.xcdatamodel diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/XMPPvCard2.xcdatamodel/elements b/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/XMPPvCard2.xcdatamodel/elements new file mode 100644 index 0000000000..33f7361169 Binary files /dev/null and b/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/XMPPvCard2.xcdatamodel/elements differ diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/XMPPvCard2.xcdatamodel/layout b/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/XMPPvCard2.xcdatamodel/layout new file mode 100644 index 0000000000..c49f8276c5 Binary files /dev/null and b/Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld/XMPPvCard2.xcdatamodel/layout differ diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCardCoreDataStorage.m b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardCoreDataStorage.m index e7b84ec7bc..bf8f7e81e4 100644 --- a/Extensions/XEP-0054/CoreDataStorage/XMPPvCardCoreDataStorage.m +++ b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardCoreDataStorage.m @@ -128,7 +128,7 @@ - (void)clearvCardTempForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream inManagedObjectContext:[self managedObjectContext]]; vCard.vCardTemp = nil; - vCard.lastUpdated = [NSDate date]; + vCard.lastUpdated = [NSDate distantPast]; }]; } @@ -170,7 +170,7 @@ - (void)setvCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid xmppStream vCard = [XMPPvCardCoreDataStorageObject fetchOrInsertvCardForJID:jid inManagedObjectContext:[self managedObjectContext]]; - vCard.waitingForFetch = [NSNumber numberWithBool:NO]; + vCard.waitingForFetch = @NO; vCard.vCardTemp = vCardTemp; // Update photo and photo hash @@ -210,7 +210,7 @@ - (BOOL)shouldFetchvCardTempForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)strea } else if (!waitingForFetch) { - vCard.waitingForFetch = [NSNumber numberWithBool:YES]; + vCard.waitingForFetch = @YES; vCard.lastUpdated = [NSDate date]; result = YES; diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTempCoreDataStorageObject.h b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTempCoreDataStorageObject.h index 2638c324f4..c658913f9b 100644 --- a/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTempCoreDataStorageObject.h +++ b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTempCoreDataStorageObject.h @@ -10,7 +10,7 @@ #import #import -#import "XMPPvcardTemp.h" +#import "XMPPvCardTemp.h" @class XMPPvCardCoreDataStorageObject; diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTransformer.h b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTransformer.h new file mode 100644 index 0000000000..53d651a0f9 --- /dev/null +++ b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTransformer.h @@ -0,0 +1,18 @@ +// +// XMPPvCardTransformer.h +// XMPPFramework +// +// Created by Tobias Ottenweller on 26.10.20. +// Copyright © 2020 XMPPFramework. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +API_AVAILABLE(ios(12.0), tvos(12.0), macos(10.14)) +@interface XMPPvCardTransformer : NSSecureUnarchiveFromDataTransformer + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTransformer.m b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTransformer.m new file mode 100644 index 0000000000..a04b1c2d4d --- /dev/null +++ b/Extensions/XEP-0054/CoreDataStorage/XMPPvCardTransformer.m @@ -0,0 +1,30 @@ +// +// XMPPvCardTransformer.m +// XMPPFramework +// +// Created by Tobias Ottenweller on 26.10.20. +// Copyright © 2020 XMPPFramework. All rights reserved. +// + +#import "XMPPvCardTransformer.h" +#import "XMPPvCardTemp.h" + +@implementation XMPPvCardTransformer + ++ (void)initialize +{ + [XMPPvCardTransformer registerTransformer]; +} + ++ (NSArray *)allowedTopLevelClasses +{ + return @[[XMPPvCardTemp class]]; +} + ++ (void)registerTransformer { + NSString* name = @"XMPPvCardTransformer"; + XMPPvCardTransformer* transformer = [[XMPPvCardTransformer alloc] init]; + [NSValueTransformer setValueTransformer:transformer forName:name]; +} + +@end diff --git a/Extensions/XEP-0054/XMPPvCardTemp.h b/Extensions/XEP-0054/XMPPvCardTemp.h index 8e419d0f25..2c6b115f84 100644 --- a/Extensions/XEP-0054/XMPPvCardTemp.h +++ b/Extensions/XEP-0054/XMPPvCardTemp.h @@ -28,7 +28,7 @@ typedef enum _XMPPvCardTempClass { XMPPvCardTempClassConfidential, } XMPPvCardTempClass; - +NS_ASSUME_NONNULL_BEGIN extern NSString *const kXMPPNSvCardTemp; extern NSString *const kXMPPvCardTempElement; @@ -42,58 +42,59 @@ extern NSString *const kXMPPvCardTempElement; @interface XMPPvCardTemp : XMPPvCardTempBase -@property (nonatomic, strong) NSDate *bday; -@property (nonatomic, strong) NSData *photo; -@property (nonatomic, strong) NSString *nickname; -@property (nonatomic, strong) NSString *formattedName; -@property (nonatomic, strong) NSString *familyName; -@property (nonatomic, strong) NSString *givenName; -@property (nonatomic, strong) NSString *middleName; -@property (nonatomic, strong) NSString *prefix; -@property (nonatomic, strong) NSString *suffix; +@property (nonatomic, strong, nullable) NSDate *bday; +@property (nonatomic, strong, nullable) NSData *photo; +@property (nonatomic, strong, nullable) NSString *nickname; +@property (nonatomic, strong, nullable) NSString *formattedName; +@property (nonatomic, strong, nullable) NSString *familyName; +@property (nonatomic, strong, nullable) NSString *givenName; +@property (nonatomic, strong, nullable) NSString *middleName; +/** This property used to collide with the NSXMLNode prefix */ +@property (nonatomic, strong, nullable) NSString *vPrefix; +@property (nonatomic, strong, nullable) NSString *suffix; -@property (nonatomic, strong) NSArray *addresses; -@property (nonatomic, strong) NSArray *labels; -@property (nonatomic, strong) NSArray *telecomsAddresses; -@property (nonatomic, strong) NSArray *emailAddresses; +@property (nonatomic, strong, nullable) NSArray *addresses; +@property (nonatomic, strong, nullable) NSArray *labels; +@property (nonatomic, strong) NSArray *telecomsAddresses; +@property (nonatomic, strong) NSArray *emailAddresses; -@property (nonatomic, strong) XMPPJID *jid; -@property (nonatomic, strong) NSString *mailer; +@property (nonatomic, strong, nullable) XMPPJID *jid; +@property (nonatomic, strong, nullable) NSString *mailer; -@property (nonatomic, strong) NSTimeZone *timeZone; -@property (nonatomic, strong) CLLocation *location; +@property (nonatomic, strong, nullable) NSTimeZone *timeZone; +@property (nonatomic, strong, nullable) CLLocation *location; -@property (nonatomic, strong) NSString *title; -@property (nonatomic, strong) NSString *role; -@property (nonatomic, strong) NSData *logo; -@property (nonatomic, strong) XMPPvCardTemp *agent; -@property (nonatomic, strong) NSString *orgName; +@property (nonatomic, strong, nullable) NSString *title; +@property (nonatomic, strong, nullable) NSString *role; +@property (nonatomic, strong, nullable) NSData *logo; +@property (nonatomic, strong, nullable) XMPPvCardTemp *agent; +@property (nonatomic, strong, nullable) NSString *orgName; /* * ORGUNITs can only be set if there is already an ORGNAME. Otherwise, changes are ignored. */ -@property (nonatomic, strong) NSArray *orgUnits; - -@property (nonatomic, strong) NSArray *categories; -@property (nonatomic, strong) NSString *note; -@property (nonatomic, strong) NSString *prodid; -@property (nonatomic, strong) NSDate *revision; -@property (nonatomic, strong) NSString *sortString; -@property (nonatomic, strong) NSString *phoneticSound; -@property (nonatomic, strong) NSData *sound; -@property (nonatomic, strong) NSString *uid; -@property (nonatomic, strong) NSString *url; -@property (nonatomic, strong) NSString *version; -@property (nonatomic, strong) NSString *desc; +@property (nonatomic, strong, nullable) NSArray *orgUnits; + +@property (nonatomic, strong, nullable) NSArray *categories; +@property (nonatomic, strong, nullable) NSString *note; +@property (nonatomic, strong, nullable) NSString *prodid; +@property (nonatomic, strong, nullable) NSDate *revision; +@property (nonatomic, strong, nullable) NSString *sortString; +@property (nonatomic, strong, nullable) NSString *phoneticSound; +@property (nonatomic, strong, nullable) NSData *sound; +@property (nonatomic, strong, nullable) NSString *uid; +@property (nonatomic, strong, nullable) NSString *url; +@property (nonatomic, strong, nullable) NSString *version; +@property (nonatomic, strong, nullable) NSString *desc; @property (nonatomic, assign) XMPPvCardTempClass privacyClass; -@property (nonatomic, strong) NSData *key; -@property (nonatomic, strong) NSString *keyType; +@property (nonatomic, strong, nullable) NSData *key; +@property (nonatomic, strong, nullable) NSString *keyType; + (XMPPvCardTemp *)vCardTempFromElement:(NSXMLElement *)element; + (XMPPvCardTemp *)vCardTemp; -+ (XMPPvCardTemp *)vCardTempSubElementFromIQ:(XMPPIQ *)iq; -+ (XMPPvCardTemp *)vCardTempCopyFromIQ:(XMPPIQ *)iq; ++ (nullable XMPPvCardTemp *)vCardTempSubElementFromIQ:(XMPPIQ *)iq; ++ (nullable XMPPvCardTemp *)vCardTempCopyFromIQ:(XMPPIQ *)iq; + (XMPPIQ *)iqvCardRequestForJID:(XMPPJID *)jid; @@ -118,3 +119,4 @@ extern NSString *const kXMPPvCardTempElement; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTemp.m b/Extensions/XEP-0054/XMPPvCardTemp.m index 63faffc488..1a391dea7d 100644 --- a/Extensions/XEP-0054/XMPPvCardTemp.m +++ b/Extensions/XEP-0054/XMPPvCardTemp.m @@ -21,8 +21,6 @@ #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; -#else - static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; #endif NSString *const kXMPPNSvCardTemp = @"vcard-temp"; @@ -258,7 +256,7 @@ - (void)setMiddleName:(NSString *)middle { } -- (NSString *)prefix { +- (NSString *)vPrefix { NSString *result = nil; NSXMLElement *name = [self elementForName:@"N"]; @@ -274,7 +272,7 @@ - (NSString *)prefix { } -- (void)setPrefix:(NSString *)prefix { +- (void)setVPrefix:(NSString *)prefix { XMPP_VCARD_SET_N_CHILD(prefix, @"PREFIX"); } @@ -317,18 +315,84 @@ - (void)setLabels:(NSArray *)labels { } - (void)clearLabels { } -- (NSArray *)telecomsAddresses { return nil; } -- (void)addTelecomsAddress:(XMPPvCardTempTel *)tel { } -- (void)removeTelecomsAddress:(XMPPvCardTempTel *)tel { } -- (void)setTelecomsAddresses:(NSArray *)tels { } -- (void)clearTelecomsAddresses { } +- (NSArray *)telecomsAddresses { + NSMutableArray *result = [NSMutableArray new]; + NSArray *tels = [self elementsForName:@"TEL"]; + for (NSXMLElement *tel in tels) { + XMPPvCardTempTel *vCardTempTel = [XMPPvCardTempTel vCardTelFromElement:tel]; + [result addObject:vCardTempTel]; + } + + return result; +} + + +- (void)addTelecomsAddress:(XMPPvCardTempTel *)tel { + [self addChild:tel]; +} + + +- (void)removeTelecomsAddress:(XMPPvCardTempTel *)tel { + NSArray *telElements = [self elementsForName:@"TEL"]; + for (NSXMLElement *element in telElements) { + XMPPvCardTempTel *vCardTempTel = [XMPPvCardTempTel vCardTelFromElement:[element copy]]; + if ([vCardTempTel.number isEqualToString:tel.number]) { + NSUInteger index = [[self children] indexOfObject:element]; + [self removeChildAtIndex:index]; + } + } +} + +- (void)setTelecomsAddresses:(NSArray *)tels { + [self clearTelecomsAddresses]; + for (XMPPvCardTempTel *tel in tels) { + [self addTelecomsAddress:tel]; + } +} + + +- (void)clearTelecomsAddresses { + [self removeElementsForName:@"TEL"]; +} -- (NSArray *)emailAddresses { return nil; } -- (void)addEmailAddress:(XMPPvCardTempEmail *)email { } -- (void)removeEmailAddress:(XMPPvCardTempEmail *)email { } -- (void)setEmailAddresses:(NSArray *)emails { } -- (void)clearEmailAddresses { } +- (NSArray *)emailAddresses { + NSMutableArray *result = [NSMutableArray new]; + NSArray *emails = [self elementsForName:@"EMAIL"]; + for (NSXMLElement *email in emails) { + XMPPvCardTempEmail *vCardTempEmail = [XMPPvCardTempEmail vCardEmailFromElement:email]; + [result addObject:vCardTempEmail]; + } + + return result; +} + +- (void)addEmailAddress:(XMPPvCardTempEmail *)email { + [self addChild:email]; +} + +- (void)removeEmailAddress:(XMPPvCardTempEmail *)email { + NSArray *emailElements = [self elementsForName:@"EMAIL"]; + for (NSXMLElement *element in emailElements) { + XMPPvCardTempEmail *vCardTempEmail = [XMPPvCardTempEmail vCardEmailFromElement:[element copy]]; + if ([vCardTempEmail.userid isEqualToString:email.userid]) { + NSUInteger index = [[self children] indexOfObject:element]; + [self removeChildAtIndex:index]; + } + } + +} + +- (void)setEmailAddresses:(NSArray *)emails { + [self clearEmailAddresses]; + for (XMPPvCardTempEmail *email in emails) { + [self addEmailAddress:email]; + } +} + +- (void)clearEmailAddresses { + [self removeElementsForName:@"EMAIL"]; +} - (XMPPJID *)jid { @@ -748,7 +812,14 @@ - (void)setPhoneticSound:(NSString *)phonetic { if (phonetic != nil) { [elem setStringValue:phonetic]; } else if (sound != nil) { - [self removeChildAtIndex:[[self children] indexOfObject:phonetic]]; + // The old code never actually did anything + // because the phonetic object is a NSString but + // the children are DDXMLNodes. + // + // [self removeChildAtIndex:[[self children] indexOfObject:phonetic]]; + + // Maybe this is what was intended? I'm not sure. + [self removeChildAtIndex:[[self children] indexOfObject:sound]]; } } @@ -858,7 +929,7 @@ - (void)setPrivacyClass:(XMPPvCardTempClass)privacyClass { } if (elem != nil) { - for (NSString *cls in [NSArray arrayWithObjects:@"PUBLIC", @"PRIVATE", @"CONFIDENTIAL", nil]) { + for (NSString *cls in @[@"PUBLIC", @"PRIVATE", @"CONFIDENTIAL"]) { NSXMLElement *priv = [elem elementForName:cls]; if (priv != nil) { [elem removeChildAtIndex:[[elem children] indexOfObject:priv]]; diff --git a/Extensions/XEP-0054/XMPPvCardTempAdr.h b/Extensions/XEP-0054/XMPPvCardTempAdr.h index 212a38af68..2f13844484 100644 --- a/Extensions/XEP-0054/XMPPvCardTempAdr.h +++ b/Extensions/XEP-0054/XMPPvCardTempAdr.h @@ -12,17 +12,18 @@ #import "XMPPvCardTempAdrTypes.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardTempAdr : XMPPvCardTempAdrTypes + (XMPPvCardTempAdr *)vCardAdrFromElement:(NSXMLElement *)elem; -@property (nonatomic, weak) NSString *pobox; -@property (nonatomic, weak) NSString *extendedAddress; -@property (nonatomic, weak) NSString *street; -@property (nonatomic, weak) NSString *locality; -@property (nonatomic, weak) NSString *region; -@property (nonatomic, weak) NSString *postalCode; -@property (nonatomic, weak) NSString *country; +@property (nonatomic, strong, nullable) NSString *pobox; +@property (nonatomic, strong, nullable) NSString *extendedAddress; +@property (nonatomic, strong, nullable) NSString *street; +@property (nonatomic, strong, nullable) NSString *locality; +@property (nonatomic, strong, nullable) NSString *region; +@property (nonatomic, strong, nullable) NSString *postalCode; +@property (nonatomic, strong, nullable) NSString *country; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTempAdr.m b/Extensions/XEP-0054/XMPPvCardTempAdr.m index 3bc4f34703..24eb38f3b8 100644 --- a/Extensions/XEP-0054/XMPPvCardTempAdr.m +++ b/Extensions/XEP-0054/XMPPvCardTempAdr.m @@ -19,8 +19,6 @@ #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; -#else - static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; #endif diff --git a/Extensions/XEP-0054/XMPPvCardTempAdrTypes.h b/Extensions/XEP-0054/XMPPvCardTempAdrTypes.h index db2b48186a..1bbf3161ab 100644 --- a/Extensions/XEP-0054/XMPPvCardTempAdrTypes.h +++ b/Extensions/XEP-0054/XMPPvCardTempAdrTypes.h @@ -12,17 +12,18 @@ #import "XMPPvCardTempBase.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardTempAdrTypes : XMPPvCardTempBase -@property (nonatomic, assign, setter=setHome:) BOOL isHome; -@property (nonatomic, assign, setter=setWork:) BOOL isWork; -@property (nonatomic, assign, setter=setParcel:) BOOL isParcel; -@property (nonatomic, assign, setter=setPostal:) BOOL isPostal; -@property (nonatomic, assign, setter=setDomestic:) BOOL isDomestic; -@property (nonatomic, assign, setter=setInternational:) BOOL isInternational; -@property (nonatomic, assign, setter=setPreferred:) BOOL isPreferred; +@property (nonatomic, assign) BOOL isHome; +@property (nonatomic, assign) BOOL isWork; +@property (nonatomic, assign) BOOL isParcel; +@property (nonatomic, assign) BOOL isPostal; +@property (nonatomic, assign) BOOL isDomestic; +@property (nonatomic, assign) BOOL isInternational; +@property (nonatomic, assign) BOOL isPreferred; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTempAdrTypes.m b/Extensions/XEP-0054/XMPPvCardTempAdrTypes.m index 02e5924ec2..e95a46c25e 100644 --- a/Extensions/XEP-0054/XMPPvCardTempAdrTypes.m +++ b/Extensions/XEP-0054/XMPPvCardTempAdrTypes.m @@ -27,7 +27,7 @@ - (BOOL)isHome { } -- (void)setHome:(BOOL)home { +- (void)setIsHome:(BOOL)home { XMPP_VCARD_SET_EMPTY_CHILD(home && ![self isHome], @"HOME"); } @@ -37,7 +37,7 @@ - (BOOL)isWork { } -- (void)setWork:(BOOL)work { +- (void)setIsWork:(BOOL)work { XMPP_VCARD_SET_EMPTY_CHILD(work && ![self isWork], @"WORK"); } @@ -47,7 +47,7 @@ - (BOOL)isParcel { } -- (void)setParcel:(BOOL)parcel { +- (void)setIsParcel:(BOOL)parcel { XMPP_VCARD_SET_EMPTY_CHILD(parcel && ![self isParcel], @"PARCEL"); } @@ -57,7 +57,7 @@ - (BOOL)isPostal { } -- (void)setPostal:(BOOL)postal { +- (void)setIsPostal:(BOOL)postal { XMPP_VCARD_SET_EMPTY_CHILD(postal && ![self isPostal], @"POSTAL"); } @@ -67,11 +67,11 @@ - (BOOL)isDomestic { } -- (void)setDomestic:(BOOL)dom { +- (void)setIsDomestic:(BOOL)dom { XMPP_VCARD_SET_EMPTY_CHILD(dom && ![self isDomestic], @"DOM"); // INTL and DOM are mutually exclusive if (dom) { - [self setInternational:NO]; + [self setIsInternational:NO]; } } @@ -81,11 +81,11 @@ - (BOOL)isInternational { } -- (void)setInternational:(BOOL)intl { +- (void)setIsInternational:(BOOL)intl { XMPP_VCARD_SET_EMPTY_CHILD(intl && ![self isInternational], @"INTL"); // INTL and DOM are mutually exclusive if (intl) { - [self setDomestic:NO]; + [self setIsDomestic:NO]; } } @@ -95,7 +95,7 @@ - (BOOL)isPreferred { } -- (void)setPreferred:(BOOL)pref { +- (void)setIsPreferred:(BOOL)pref { XMPP_VCARD_SET_EMPTY_CHILD(pref && ![self isPreferred], @"PREF"); } diff --git a/Extensions/XEP-0054/XMPPvCardTempBase.h b/Extensions/XEP-0054/XMPPvCardTempBase.h index c48ffa37d5..8d0450115d 100644 --- a/Extensions/XEP-0054/XMPPvCardTempBase.h +++ b/Extensions/XEP-0054/XMPPvCardTempBase.h @@ -61,9 +61,10 @@ [name removeChildAtIndex:[[self children] indexOfObject:part]]; \ } - -@interface XMPPvCardTempBase : NSXMLElement { +NS_ASSUME_NONNULL_BEGIN +@interface XMPPvCardTempBase : NSXMLElement { } @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTempBase.m b/Extensions/XEP-0054/XMPPvCardTempBase.m index 6578205694..777328cede 100644 --- a/Extensions/XEP-0054/XMPPvCardTempBase.m +++ b/Extensions/XEP-0054/XMPPvCardTempBase.m @@ -38,15 +38,23 @@ - (id)replacementObjectForPortCoder:(NSPortCoder *)encoder - (id)initWithCoder:(NSCoder *)coder { - NSString *xmlString; - if([coder allowsKeyedCoding]) - { - xmlString = [coder decodeObjectForKey:@"xmlString"]; - } - else - { - xmlString = [coder decodeObject]; - } + NSString *xmlString; + if([coder allowsKeyedCoding]) + { + if([coder respondsToSelector:@selector(requiresSecureCoding)] && + [coder requiresSecureCoding]) + { + xmlString = [coder decodeObjectOfClass:[NSString class] forKey:@"xmlString"]; + } + else + { + xmlString = [coder decodeObjectForKey:@"xmlString"]; + } + } + else + { + xmlString = [coder decodeObject]; + } // The method [super initWithXMLString:error:] may return a different self. // In other words, it may [self release], and alloc/init/return a new self. @@ -78,6 +86,11 @@ - (void)encodeWithCoder:(NSCoder *)coder } } ++ (BOOL) supportsSecureCoding +{ + return YES; +} + - (id)copyWithZone:(NSZone *)zone { NSXMLElement *elementCopy = [super copyWithZone:zone]; diff --git a/Extensions/XEP-0054/XMPPvCardTempEmail.h b/Extensions/XEP-0054/XMPPvCardTempEmail.h index 214efff848..15423e77ad 100644 --- a/Extensions/XEP-0054/XMPPvCardTempEmail.h +++ b/Extensions/XEP-0054/XMPPvCardTempEmail.h @@ -12,18 +12,19 @@ #import "XMPPvCardTempBase.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardTempEmail : XMPPvCardTempBase + (XMPPvCardTempEmail *)vCardEmailFromElement:(NSXMLElement *)elem; -@property (nonatomic, assign, setter=setHome:) BOOL isHome; -@property (nonatomic, assign, setter=setWork:) BOOL isWork; -@property (nonatomic, assign, setter=setInternet:) BOOL isInternet; -@property (nonatomic, assign, setter=setX400:) BOOL isX400; -@property (nonatomic, assign, setter=setPreferred:) BOOL isPreferred; +@property (nonatomic, assign) BOOL isHome; +@property (nonatomic, assign) BOOL isWork; +@property (nonatomic, assign) BOOL isInternet; +@property (nonatomic, assign) BOOL isX400; +@property (nonatomic, assign) BOOL isPreferred; -@property (nonatomic, weak) NSString *userid; +@property (nonatomic, strong, nullable) NSString *userid; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTempEmail.m b/Extensions/XEP-0054/XMPPvCardTempEmail.m index 609948bd4c..68c698ba29 100644 --- a/Extensions/XEP-0054/XMPPvCardTempEmail.m +++ b/Extensions/XEP-0054/XMPPvCardTempEmail.m @@ -19,8 +19,6 @@ #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; -#else - static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; #endif @@ -69,7 +67,7 @@ - (BOOL)isHome { } -- (void)setHome:(BOOL)home { +- (void)setIsHome:(BOOL)home { XMPP_VCARD_SET_EMPTY_CHILD(home && ![self isHome], @"HOME"); } @@ -79,7 +77,7 @@ - (BOOL)isWork { } -- (void)setWork:(BOOL)work { +- (void)setIsWork:(BOOL)work { XMPP_VCARD_SET_EMPTY_CHILD(work && ![self isWork], @"WORK"); } @@ -89,7 +87,7 @@ - (BOOL)isInternet { } -- (void)setInternet:(BOOL)internet { +- (void)setIsInternet:(BOOL)internet { XMPP_VCARD_SET_EMPTY_CHILD(internet && ![self isInternet], @"INTERNET"); } @@ -99,7 +97,7 @@ - (BOOL)isX400 { } -- (void)setX400:(BOOL)x400 { +- (void)setIsX400:(BOOL)x400 { XMPP_VCARD_SET_EMPTY_CHILD(x400 && ![self isX400], @"X400"); } @@ -109,7 +107,7 @@ - (BOOL)isPreferred { } -- (void)setPreferred:(BOOL)pref { +- (void)setIsPreferred:(BOOL)pref { XMPP_VCARD_SET_EMPTY_CHILD(pref && ![self isPreferred], @"PREF"); } diff --git a/Extensions/XEP-0054/XMPPvCardTempLabel.h b/Extensions/XEP-0054/XMPPvCardTempLabel.h index 39240612ce..47ad8ebd43 100644 --- a/Extensions/XEP-0054/XMPPvCardTempLabel.h +++ b/Extensions/XEP-0054/XMPPvCardTempLabel.h @@ -12,14 +12,15 @@ #import "XMPPvCardTempAdrTypes.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardTempLabel : XMPPvCardTempAdrTypes -@property (nonatomic, weak) NSArray *lines; +@property (nonatomic, strong, nullable) NSArray *lines; + (XMPPvCardTempLabel *)vCardLabelFromElement:(NSXMLElement *)elem; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTempLabel.m b/Extensions/XEP-0054/XMPPvCardTempLabel.m index 26f987714f..2e4875c60b 100644 --- a/Extensions/XEP-0054/XMPPvCardTempLabel.m +++ b/Extensions/XEP-0054/XMPPvCardTempLabel.m @@ -19,8 +19,6 @@ #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; -#else - static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; #endif diff --git a/Extensions/XEP-0054/XMPPvCardTempModule.h b/Extensions/XEP-0054/XMPPvCardTempModule.h index 01c1bc0179..0b7bcd64fb 100755 --- a/Extensions/XEP-0054/XMPPvCardTempModule.h +++ b/Extensions/XEP-0054/XMPPvCardTempModule.h @@ -17,19 +17,16 @@ @protocol XMPPvCardTempModuleStorage; - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardTempModule : XMPPModule -{ - id __strong _xmppvCardTempModuleStorage; - XMPPIDTracker *_myvCardTracker; -} - @property(nonatomic, strong, readonly) id xmppvCardTempModuleStorage; -@property(nonatomic, strong, readonly) XMPPvCardTemp *myvCardTemp; +@property(nonatomic, strong, readonly, nullable) XMPPvCardTemp *myvCardTemp; -- (id)initWithvCardStorage:(id )storage; -- (id)initWithvCardStorage:(id )storage dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; +- (instancetype)initWithvCardStorage:(id )storage; +- (instancetype)initWithvCardStorage:(id )storage dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; /** * Fetches the vCardTemp for the given JID if it is not in the storage @@ -45,7 +42,7 @@ * Returns the vCardTemp for the given JID, this is the equivalent of calling the vCardTempForJID:xmppStream: on the moduleStorage * If there is no vCardTemp in the storage for the given jid and shouldFetch is YES, it will automatically fetch it from the network **/ -- (XMPPvCardTemp *)vCardTempForJID:(XMPPJID *)jid shouldFetch:(BOOL)shouldFetch; +- (nullable XMPPvCardTemp *)vCardTempForJID:(XMPPJID *)jid shouldFetch:(BOOL)shouldFetch; /** * Updates myvCard in storage and sends it to the server @@ -65,9 +62,13 @@ didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid; +- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule + failedToFetchvCardForJID:(XMPPJID *)jid + error:(nullable NSXMLElement*)error; + - (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule; -- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(NSXMLElement *)error; +- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(nullable NSXMLElement *)error; @end @@ -96,7 +97,7 @@ /** * Returns a vCardTemp object or nil **/ -- (XMPPvCardTemp *)vCardTempForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; +- (nullable XMPPvCardTemp *)vCardTempForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; /** * Used to set the vCardTemp object when we get it from the XMPP server. @@ -106,7 +107,7 @@ /** * Returns My vCardTemp object or nil **/ -- (XMPPvCardTemp *)myvCardTempForXMPPStream:(XMPPStream *)stream; +- (nullable XMPPvCardTemp *)myvCardTempForXMPPStream:(XMPPStream *)stream; /** * Asks the backend if we should fetch the vCardTemp from the network. @@ -115,3 +116,5 @@ - (BOOL)shouldFetchvCardTempForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; @end +NS_ASSUME_NONNULL_END + diff --git a/Extensions/XEP-0054/XMPPvCardTempModule.m b/Extensions/XEP-0054/XMPPvCardTempModule.m index 38d5021ff4..a411db665b 100755 --- a/Extensions/XEP-0054/XMPPvCardTempModule.m +++ b/Extensions/XEP-0054/XMPPvCardTempModule.m @@ -24,7 +24,10 @@ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; #endif -@interface XMPPvCardTempModule() +@interface XMPPvCardTempModule() { + id __strong _xmppvCardTempModuleStorage; + XMPPIDTracker *_myvCardTracker; +} - (void)_updatevCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid; - (void)_fetchvCardTempForJID:(XMPPJID *)jid; @@ -39,22 +42,6 @@ @implementation XMPPvCardTempModule @synthesize xmppvCardTempModuleStorage = _xmppvCardTempModuleStorage; -- (id)init -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPvCardTempModule.h are supported. - - return [self initWithvCardStorage:nil dispatchQueue:NULL]; -} - -- (id)initWithDispatchQueue:(dispatch_queue_t)queue -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPvCardTempModule.h are supported. - - return [self initWithvCardStorage:nil dispatchQueue:NULL]; -} - - (id)initWithvCardStorage:(id )storage { return [self initWithvCardStorage:storage dispatchQueue:NULL]; @@ -98,8 +85,8 @@ - (void)deactivate dispatch_block_t block = ^{ @autoreleasepool { - [_myvCardTracker removeAllIDs]; - _myvCardTracker = nil; + [self->_myvCardTracker removeAllIDs]; + self->_myvCardTracker = nil; }}; @@ -134,10 +121,10 @@ - (void)fetchvCardTempForJID:(XMPPJID *)jid ignoreStorage:(BOOL)ignoreStorage if (!ignoreStorage) { // Try loading from storage - vCardTemp = [_xmppvCardTempModuleStorage vCardTempForJID:jid xmppStream:xmppStream]; + vCardTemp = [self->_xmppvCardTempModuleStorage vCardTempForJID:jid xmppStream:self->xmppStream]; } - if (vCardTemp == nil && [_xmppvCardTempModuleStorage shouldFetchvCardTempForJID:jid xmppStream:xmppStream]) + if (vCardTemp == nil && [self->_xmppvCardTempModuleStorage shouldFetchvCardTempForJID:jid xmppStream:self->xmppStream]) { [self _fetchvCardTempForJID:jid]; } @@ -156,9 +143,9 @@ - (XMPPvCardTemp *)vCardTempForJID:(XMPPJID *)jid shouldFetch:(BOOL)shouldFetch{ dispatch_block_t block = ^{ @autoreleasepool { - XMPPvCardTemp *vCardTemp = [_xmppvCardTempModuleStorage vCardTempForJID:jid xmppStream:xmppStream]; + XMPPvCardTemp *vCardTemp = [self->_xmppvCardTempModuleStorage vCardTempForJID:jid xmppStream:self->xmppStream]; - if (vCardTemp == nil && shouldFetch && [_xmppvCardTempModuleStorage shouldFetchvCardTempForJID:jid xmppStream:xmppStream]) + if (vCardTemp == nil && shouldFetch && [self->_xmppvCardTempModuleStorage shouldFetchvCardTempForJID:jid xmppStream:self->xmppStream]) { [self _fetchvCardTempForJID:jid]; } @@ -186,15 +173,15 @@ - (void)updateMyvCardTemp:(XMPPvCardTemp *)vCardTemp XMPPvCardTemp *newvCardTemp = [vCardTemp copy]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:[xmppStream generateUUID] child:newvCardTemp]; - [xmppStream sendElement:iq]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:[self->xmppStream generateUUID] child:newvCardTemp]; + [self->xmppStream sendElement:iq]; - [_myvCardTracker addElement:iq + [self->_myvCardTracker addElement:iq target:self selector:@selector(handleMyvcard:withInfo:) timeout:600]; - [self _updatevCardTemp:newvCardTemp forJID:[xmppStream myJID]]; + [self _updatevCardTemp:newvCardTemp forJID:[self->xmppStream myJID]]; }}; @@ -218,9 +205,9 @@ - (void)_updatevCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid XMPPLogVerbose(@"%@: %s %@", THIS_FILE, __PRETTY_FUNCTION__, [jid bare]); - [_xmppvCardTempModuleStorage setvCardTemp:vCardTemp forJID:jid xmppStream:xmppStream]; + [self->_xmppvCardTempModuleStorage setvCardTemp:vCardTemp forJID:jid xmppStream:self->xmppStream]; - [(id )multicastDelegate xmppvCardTempModule:self + [(id )self->multicastDelegate xmppvCardTempModule:self didReceivevCardTemp:vCardTemp forJID:jid]; }}; @@ -233,8 +220,16 @@ - (void)_updatevCardTemp:(XMPPvCardTemp *)vCardTemp forJID:(XMPPJID *)jid - (void)_fetchvCardTempForJID:(XMPPJID *)jid{ if(!jid) return; + - [xmppStream sendElement:[XMPPvCardTemp iqvCardRequestForJID:jid]]; + XMPPIQ *iq = [XMPPvCardTemp iqvCardRequestForJID:jid]; + + [_myvCardTracker addElement:iq + target:self + selector:@selector(handleFetchvCard:withInfo:) + timeout:600]; + + [xmppStream sendElement:iq]; } - (void)handleMyvcard:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)trackerInfo{ @@ -247,8 +242,30 @@ - (void)handleMyvcard:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)trackerInfo { NSXMLElement *errorElement = [iq elementForName:@"error"]; [(id )multicastDelegate xmppvCardTempModule:self failedToUpdateMyvCard:errorElement]; - } + } +} +- (void)handleFetchvCard:(XMPPIQ*)iq withInfo:(XMPPBasicTrackingInfo*)trackerInfo { + XMPPJID *jid = trackerInfo.element.to; + // If JID was omitted from request, you were fetching your own vCard + if (!jid) { + jid = xmppStream.myJID; + } + if([iq isErrorIQ]) + { + NSXMLElement *errorElement = [iq elementForName:@"error"]; + [(id )multicastDelegate xmppvCardTempModule:self failedToFetchvCardForJID:jid error:errorElement]; + } else if([iq isResultIQ]) { + NSXMLElement *vCard = [[iq elementForName:@"vCard"] copy]; + if (vCard.childCount == 0) { + [(id )multicastDelegate xmppvCardTempModule:self failedToFetchvCardForJID:jid error:nil]; + } else if (![iq from]) { + // If there's no fromJID, it means the vCard was already within didReceiveIQ, and this is + // the vCard for yourself + XMPPvCardTemp *vCardTemp = [XMPPvCardTemp vCardTempFromElement:vCard]; + [self _updatevCardTemp:vCardTemp forJID:jid]; + } + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -259,7 +276,12 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { // This method is invoked on the moduleQueue. - [_myvCardTracker invokeForElement:iq withObject:iq]; + if (!iq.from) { + // Some error responses for self or contacts don't have a "from" + [_myvCardTracker invokeForID:iq.elementID withObject:iq]; + } else { + [_myvCardTracker invokeForElement:iq withObject:iq]; + } // Remember XML heirarchy memory management rules. // The passed parameter is a subnode of the IQ, and we need to pass it to an asynchronous operation. diff --git a/Extensions/XEP-0054/XMPPvCardTempTel.h b/Extensions/XEP-0054/XMPPvCardTempTel.h index ba6df21f95..f3cbf6fd4a 100644 --- a/Extensions/XEP-0054/XMPPvCardTempTel.h +++ b/Extensions/XEP-0054/XMPPvCardTempTel.h @@ -12,26 +12,27 @@ #import "XMPPvCardTempBase.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardTempTel : XMPPvCardTempBase + (XMPPvCardTempTel *)vCardTelFromElement:(NSXMLElement *)elem; -@property (nonatomic, assign, setter=setHome:) BOOL isHome; -@property (nonatomic, assign, setter=setWork:) BOOL isWork; -@property (nonatomic, assign, setter=setVoice:) BOOL isVoice; -@property (nonatomic, assign, setter=setFax:) BOOL isFax; -@property (nonatomic, assign, setter=setPager:) BOOL isPager; -@property (nonatomic, assign, setter=setMessaging:) BOOL hasMessaging; -@property (nonatomic, assign, setter=setCell:) BOOL isCell; -@property (nonatomic, assign, setter=setVideo:) BOOL isVideo; -@property (nonatomic, assign, setter=setBBS:) BOOL isBBS; -@property (nonatomic, assign, setter=setModem:) BOOL isModem; -@property (nonatomic, assign, setter=setISDN:) BOOL isISDN; -@property (nonatomic, assign, setter=setPCS:) BOOL isPCS; -@property (nonatomic, assign, setter=setPreferred:) BOOL isPreferred; - -@property (nonatomic, weak) NSString *number; +@property (nonatomic, assign) BOOL isHome; +@property (nonatomic, assign) BOOL isWork; +@property (nonatomic, assign) BOOL isVoice; +@property (nonatomic, assign) BOOL isFax; +@property (nonatomic, assign) BOOL isPager; +@property (nonatomic, assign) BOOL hasMessaging; +@property (nonatomic, assign) BOOL isCell; +@property (nonatomic, assign) BOOL isVideo; +@property (nonatomic, assign) BOOL isBBS; +@property (nonatomic, assign) BOOL isModem; +@property (nonatomic, assign) BOOL isISDN; +@property (nonatomic, assign) BOOL isPCS; +@property (nonatomic, assign) BOOL isPreferred; + +@property (nonatomic, nullable) NSString *number; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0054/XMPPvCardTempTel.m b/Extensions/XEP-0054/XMPPvCardTempTel.m index 1d883a8436..6fb14b7e8b 100644 --- a/Extensions/XEP-0054/XMPPvCardTempTel.m +++ b/Extensions/XEP-0054/XMPPvCardTempTel.m @@ -19,8 +19,6 @@ #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; -#else - static const int xmppLogLevel = XMPP_LOG_LEVEL_ERROR; #endif @@ -68,7 +66,7 @@ - (BOOL)isHome { } -- (void)setHome:(BOOL)home { +- (void)setIsHome:(BOOL)home { XMPP_VCARD_SET_EMPTY_CHILD(home && ![self isHome], @"HOME"); } @@ -78,7 +76,7 @@ - (BOOL)isWork { } -- (void)setWork:(BOOL)work { +- (void)setIsWork:(BOOL)work { XMPP_VCARD_SET_EMPTY_CHILD(work && ![self isWork], @"WORK"); } @@ -88,7 +86,7 @@ - (BOOL)isVoice { } -- (void)setVoice:(BOOL)voice { +- (void)setIsVoice:(BOOL)voice { XMPP_VCARD_SET_EMPTY_CHILD(voice && ![self isVoice], @"VOICE"); } @@ -98,7 +96,7 @@ - (BOOL)isFax { } -- (void)setFax:(BOOL)fax { +- (void)setIsFax:(BOOL)fax { XMPP_VCARD_SET_EMPTY_CHILD(fax && ![self isFax], @"FAX"); } @@ -108,7 +106,7 @@ - (BOOL)isPager { } -- (void)setPager:(BOOL)pager { +- (void)setIsPager:(BOOL)pager { XMPP_VCARD_SET_EMPTY_CHILD(pager && ![self isPager], @"PAGER"); } @@ -118,8 +116,8 @@ - (BOOL)hasMessaging { } -- (void)setMessaging:(BOOL)msg { - XMPP_VCARD_SET_EMPTY_CHILD(msg && ![self hasMessaging], @"MSG"); +- (void)setHasMessaging:(BOOL)hasMessaging { + XMPP_VCARD_SET_EMPTY_CHILD(hasMessaging && ![self hasMessaging], @"MSG"); } @@ -128,7 +126,7 @@ - (BOOL)isCell { } -- (void)setCell:(BOOL)cell { +- (void)setIsCell:(BOOL)cell { XMPP_VCARD_SET_EMPTY_CHILD(cell && ![self isCell], @"CELL"); } @@ -138,7 +136,7 @@ - (BOOL)isVideo { } -- (void)setVideo:(BOOL)video { +- (void)setIsVideo:(BOOL)video { XMPP_VCARD_SET_EMPTY_CHILD(video && ![self isVideo], @"VIDEO"); } @@ -148,7 +146,7 @@ - (BOOL)isBBS { } -- (void)setBBS:(BOOL)bbs { +- (void)setIsBBS:(BOOL)bbs { XMPP_VCARD_SET_EMPTY_CHILD(bbs && ![self isBBS], @"BBS"); } @@ -158,7 +156,7 @@ - (BOOL)isModem { } -- (void)setModem:(BOOL)modem { +- (void)setIsModem:(BOOL)modem { XMPP_VCARD_SET_EMPTY_CHILD(modem && ![self isModem], @"MODEM"); } @@ -168,7 +166,7 @@ - (BOOL)isISDN { } -- (void)setISDN:(BOOL)isdn { +- (void)setIsISDN:(BOOL)isdn { XMPP_VCARD_SET_EMPTY_CHILD(isdn && ![self isISDN], @"ISDN"); } @@ -178,7 +176,7 @@ - (BOOL)isPCS { } -- (void)setPCS:(BOOL)pcs { +- (void)setIsPCS:(BOOL)pcs { XMPP_VCARD_SET_EMPTY_CHILD(pcs && ![self isPCS], @"PCS"); } @@ -188,7 +186,7 @@ - (BOOL)isPreferred { } -- (void)setPreferred:(BOOL)pref { +- (void)setIsPreferred:(BOOL)pref { XMPP_VCARD_SET_EMPTY_CHILD(pref && ![self isPreferred], @"PREF"); } diff --git a/Extensions/XEP-0059/NSXMLElement+XEP_0059.h b/Extensions/XEP-0059/NSXMLElement+XEP_0059.h index fa8db51b63..3572808490 100644 --- a/Extensions/XEP-0059/NSXMLElement+XEP_0059.h +++ b/Extensions/XEP-0059/NSXMLElement+XEP_0059.h @@ -1,16 +1,15 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; @class XMPPResultSet; - +NS_ASSUME_NONNULL_BEGIN @interface NSXMLElement (XEP_0059) -- (BOOL)isResultSet; -- (BOOL)hasResultSet; -- (XMPPResultSet *)resultSet; +@property (nonatomic, readonly) BOOL isResultSet; +@property (nonatomic, readonly) BOOL hasResultSet; +@property (nonatomic, readonly, nullable) XMPPResultSet *resultSet; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0059/XMPPResultSet.h b/Extensions/XEP-0059/XMPPResultSet.h index c72c7633e3..82bb562457 100644 --- a/Extensions/XEP-0059/XMPPResultSet.h +++ b/Extensions/XEP-0059/XMPPResultSet.h @@ -1,8 +1,6 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; /** * The XMPPResultSet class represents an element form XEP-0059. @@ -30,6 +28,7 @@ * [XMPPResultSet resultSetWithMax:10 firstIndex:NSNotFound after:nil before:@""]; **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPResultSet : NSXMLElement /** @@ -48,49 +47,50 @@ firstIndex:(NSInteger)index; + (XMPPResultSet *)resultSetWithMax:(NSInteger)max - after:(NSString *)after; + after:(nullable NSString *)after; + (XMPPResultSet *)resultSetWithMax:(NSInteger)max - before:(NSString *)before; + before:(nullable NSString *)before; + (XMPPResultSet *)resultSetWithMax:(NSInteger)max firstIndex:(NSInteger)firstIndex - after:(NSString *)after - before:(NSString *)before; + after:(nullable NSString *)after + before:(nullable NSString *)before; /** * Creates and returns a new XMPPResultSet element. **/ -- (id)init; +- (instancetype)init; -- (id)initWithMax:(NSInteger)max; +- (instancetype)initWithMax:(NSInteger)max; -- (id)initWithMax:(NSInteger)max - firstIndex:(NSInteger)firstIndex; +- (instancetype)initWithMax:(NSInteger)max + firstIndex:(NSInteger)firstIndex; -- (id)initWithMax:(NSInteger)max - after:(NSString *)after; +- (instancetype)initWithMax:(NSInteger)max + after:(nullable NSString *)after; -- (id)initWithMax:(NSInteger)max - before:(NSString *)before; +- (instancetype)initWithMax:(NSInteger)max + before:(nullable NSString *)before; -- (id)initWithMax:(NSInteger)max - firstIndex:(NSInteger)firstIndex - after:(NSString *)after - before:(NSString *)before; +- (instancetype)initWithMax:(NSInteger)max + firstIndex:(NSInteger)firstIndex + after:(nullable NSString *)after + before:(nullable NSString *)before; -- (NSInteger)max; +@property (nonatomic, readonly) NSInteger max; -- (NSInteger)firstIndex; +@property (nonatomic, readonly) NSInteger firstIndex; -- (NSString *)after; -- (NSString *)before; +@property (nonatomic, readonly, nullable) NSString *after; +@property (nonatomic, readonly, nullable) NSString *before; -- (NSInteger)count; +@property (nonatomic, readonly) NSInteger count; -- (NSString *)first; -- (NSString *)last; +@property (nonatomic, readonly, nullable) NSString *first; +@property (nonatomic, readonly, nullable) NSString *last; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0059/XMPPResultSet.m b/Extensions/XEP-0059/XMPPResultSet.m index 7d9be02aed..87ceb0395c 100644 --- a/Extensions/XEP-0059/XMPPResultSet.m +++ b/Extensions/XEP-0059/XMPPResultSet.m @@ -117,13 +117,13 @@ - (id)initWithMax:(NSInteger)max { if(max != NSNotFound) { - NSXMLElement *maxElement = [NSXMLElement elementWithName:@"max" stringValue:[[NSNumber numberWithInteger:max] stringValue]]; + NSXMLElement *maxElement = [NSXMLElement elementWithName:@"max" stringValue:[@(max) stringValue]]; [self addChild:maxElement]; } if(firstIndex != NSNotFound) { - NSXMLElement *maxElement = [NSXMLElement elementWithName:@"index" stringValue:[[NSNumber numberWithInteger:firstIndex] stringValue]]; + NSXMLElement *maxElement = [NSXMLElement elementWithName:@"index" stringValue:[@(firstIndex) stringValue]]; [self addChild:maxElement]; } diff --git a/Extensions/XEP-0060/XMPPPubSub.h b/Extensions/XEP-0060/XMPPPubSub.h index 40f231095c..5f5e1f64d3 100644 --- a/Extensions/XEP-0060/XMPPPubSub.h +++ b/Extensions/XEP-0060/XMPPPubSub.h @@ -3,6 +3,7 @@ #define _XMPP_PUB_SUB_H +NS_ASSUME_NONNULL_BEGIN @interface XMPPPubSub : XMPPModule /** @@ -21,13 +22,31 @@ * However, the exact format of the JID varies from server to server, and is often configurable. * If you don't know the PubSub JID beforehand, you may need to use service discovery to find it. **/ -- (id)initWithServiceJID:(XMPPJID *)aServiceJID; -- (id)initWithServiceJID:(XMPPJID *)aServiceJID dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; +- (instancetype)initWithServiceJID:(nullable XMPPJID *)aServiceJID; +- (instancetype)initWithServiceJID:(nullable XMPPJID *)aServiceJID dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; /** * The JID of the PubSub server the module is to communicate with. **/ -@property (nonatomic, strong, readonly) XMPPJID *serviceJID; +@property (nonatomic, strong, readonly, nullable) XMPPJID *serviceJID; + +/** + * An array of publisher JIDs whose event messages will be received by the module. + * Only effective for PEP modules (serviceJID == nil). If the value is nil (the default), publisher filter is not applied. + * This filter is applied together with the node-based one. + * If both this and pepNodes are nil, only own events will be received. +**/ +@property (atomic, copy, readwrite, nullable) NSArray *pepPublisherJIDs; + +/** + * An array of nodes whose event messages will be received by the module. + * Only effective for PEP modules (serviceJID == nil). If the value is nil (the default), node filter is not applied. + * This filter is applied together with the publisher-based one. + * If both this and pepPublisherJIDs are nil, only own events will be received. +**/ +@property (atomic, copy, readwrite, nullable) NSArray *pepNodes; /** * Sends a subscription request for the given node name. @@ -74,8 +93,8 @@ * @see xmppPubSub:didNotSubscribeToNode:withError: **/ - (NSString *)subscribeToNode:(NSString *)node; -- (NSString *)subscribeToNode:(NSString *)node withJID:(XMPPJID *)myBareOrFullJid; -- (NSString *)subscribeToNode:(NSString *)node withJID:(XMPPJID *)myBareOrFullJid options:(NSDictionary *)options; +- (NSString *)subscribeToNode:(NSString *)node withJID:(nullable XMPPJID *)myBareOrFullJid; +- (NSString *)subscribeToNode:(NSString *)node withJID:(nullable XMPPJID *)myBareOrFullJid options:(nullable NSDictionary *)options; /** * Sends an unsubscribe request for the given node name. @@ -112,8 +131,8 @@ * @see xmppPubSub:didNotUnsubscribeFromNode:(NSString *)node withError: **/ - (NSString *)unsubscribeFromNode:(NSString *)node; -- (NSString *)unsubscribeFromNode:(NSString *)node withJID:(XMPPJID *)myBareOrFullJid; -- (NSString *)unsubscribeFromNode:(NSString *)node withJID:(XMPPJID *)myBareOrFullJid subid:(NSString *)subid; +- (NSString *)unsubscribeFromNode:(NSString *)node withJID:(nullable XMPPJID *)myBareOrFullJid; +- (NSString *)unsubscribeFromNode:(NSString *)node withJID:(nullable XMPPJID *)myBareOrFullJid subid:(nullable NSString *)subid; /** * Fetches the current PubSub subscriptions from the server. @@ -139,7 +158,7 @@ * @see xmppPubSub:didNotRetrieveSubscriptions:forNode: **/ - (NSString *)retrieveSubscriptions; -- (NSString *)retrieveSubscriptionsForNode:(NSString *)node; +- (NSString *)retrieveSubscriptionsForNode:(nullable NSString *)node; /** * @param node @@ -174,9 +193,9 @@ * @see xmppPubSub:didNotConfigureSubscriptionToNode:withError: **/ - (NSString *)configureSubscriptionToNode:(NSString *)node - withJID:(XMPPJID *)myBareOrFullJid - subid:(NSString *)subid - options:(NSDictionary *)options; + withJID:(nullable XMPPJID *)myBareOrFullJid + subid:(nullable NSString *)subid + options:(nullable NSDictionary *)options; /** * Publishes the entry to the given node. @@ -234,11 +253,11 @@ * @see xmppPubSub:didNotPublishToNode:withError: **/ - (NSString *)publishToNode:(NSString *)node entry:(NSXMLElement *)entry; -- (NSString *)publishToNode:(NSString *)node entry:(NSXMLElement *)entry withItemID:(NSString *)itemId; +- (NSString *)publishToNode:(NSString *)node entry:(NSXMLElement *)entry withItemID:(nullable NSString *)itemId; - (NSString *)publishToNode:(NSString *)node entry:(NSXMLElement *)entry - withItemID:(NSString *)itemId - options:(NSDictionary *)options; + withItemID:(nullable NSString *)itemId + options:(nullable NSDictionary *)options; /** * Creates the given node with optional options. @@ -263,7 +282,7 @@ * @see xmppPubSub:didNotCreateNode:withError: **/ - (NSString *)createNode:(NSString *)node; -- (NSString *)createNode:(NSString *)node withOptions:(NSDictionary *)options; +- (NSString *)createNode:(NSString *)node withOptions:(nullable NSDictionary *)options; /** * Deletes the given node. @@ -305,7 +324,34 @@ * @see xmppPubSub:didConfigureNode:withIQ: * @see xmppPubSub:didNotConfigureNode:withError: **/ -- (NSString *)configureNode:(NSString *)node withOptions:(NSDictionary *)options; +- (NSString *)configureNode:(NSString *)node withOptions:(nullable NSDictionary *)options; + +/** + * Retrieves items from given node. + * + * @param node + * + * The name of the node to retrieve items from. + * This should be the same node name you used when you created it. + * + * @param withItemIDs + * + * This corresponds to a list of unique ids of previously published items. + * The server will return the previously published items for those that exists. + * If none of the items exists, an empty items list will be returned. + * If you don't pass any itemIDs, the server will retrieve all items on the given node + * + * @return uuid + * + * The return value is the unique elementID of the IQ stanza that was sent. + * + * The server's response to the request will be reported via the appropriate delegate methods. + * + * @see xmppPubSub:didRetrieveItems:fromNode: + * @see xmppPubSub:didNotRetrieveItems:fromNode: + **/ +- (NSString *)retrieveItemsFromNode:(NSString *)node; +- (NSString *)retrieveItemsFromNode:(NSString *)node withItemIDs:(nullable NSArray *)itemIds; @end @@ -339,6 +385,10 @@ - (void)xmppPubSub:(XMPPPubSub *)sender didConfigureNode:(NSString *)node withResult:(XMPPIQ *)iq; - (void)xmppPubSub:(XMPPPubSub *)sender didNotConfigureNode:(NSString *)node withError:(XMPPIQ *)iq; +- (void)xmppPubSub:(XMPPPubSub *)sender didRetrieveItems:(XMPPIQ *)iq fromNode:(NSString *)node; +- (void)xmppPubSub:(XMPPPubSub *)sender didNotRetrieveItems:(XMPPIQ *)iq fromNode:(NSString *)node; + - (void)xmppPubSub:(XMPPPubSub *)sender didReceiveMessage:(XMPPMessage *)message; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0060/XMPPPubSub.m b/Extensions/XEP-0060/XMPPPubSub.m index 5520e1c489..ff509b50af 100644 --- a/Extensions/XEP-0060/XMPPPubSub.m +++ b/Extensions/XEP-0060/XMPPPubSub.m @@ -18,15 +18,19 @@ @implementation XMPPPubSub { XMPPJID *serviceJID; XMPPJID *myJID; + + NSArray *pepPublisherJIDs; + NSArray *pepNodes; NSMutableDictionary *subscribeDict; NSMutableDictionary *unsubscribeDict; - NSMutableDictionary *retrieveDict; + NSMutableDictionary *retrieveSubsDict; NSMutableDictionary *configSubDict; NSMutableDictionary *createDict; NSMutableDictionary *deleteDict; NSMutableDictionary *configNodeDict; NSMutableDictionary *publishDict; + NSMutableDictionary *retrieveItemsDict; } + (BOOL)isPubSubMessage:(XMPPMessage *)message @@ -37,20 +41,64 @@ + (BOOL)isPubSubMessage:(XMPPMessage *)message @synthesize serviceJID; -- (id)init +- (NSArray *)pepPublisherJIDs { - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPPubSub.h are supported. - - return [self initWithServiceJID:nil dispatchQueue:NULL]; + __block NSArray *result; + + dispatch_block_t block = ^{ + result = self->pepPublisherJIDs; + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + return result; } -- (id)initWithDispatchQueue:(dispatch_queue_t)queue +- (void)setPepPublisherJIDs:(NSArray *)pepPublisherJIDs { - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPPubSub.h are supported. - - return [self initWithServiceJID:nil dispatchQueue:NULL]; + NSArray *newValue = [pepPublisherJIDs copy]; + + dispatch_block_t block = ^{ + self->pepPublisherJIDs = newValue; + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (NSArray *)pepNodes +{ + __block NSArray *result; + + dispatch_block_t block = ^{ + result = self->pepNodes; + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + return result; +} + +- (void)setPepNodes:(NSArray *)pepNodes +{ + NSArray *newValue = [pepNodes copy]; + + dispatch_block_t block = ^{ + self->pepNodes = newValue; + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); } - (id)initWithServiceJID:(XMPPJID *)aServiceJID @@ -67,14 +115,15 @@ - (id)initWithServiceJID:(XMPPJID *)aServiceJID dispatchQueue:(dispatch_queue_t) { serviceJID = [aServiceJID copy]; - subscribeDict = [[NSMutableDictionary alloc] init]; - unsubscribeDict = [[NSMutableDictionary alloc] init]; - retrieveDict = [[NSMutableDictionary alloc] init]; - configSubDict = [[NSMutableDictionary alloc] init]; - createDict = [[NSMutableDictionary alloc] init]; - deleteDict = [[NSMutableDictionary alloc] init]; - configNodeDict = [[NSMutableDictionary alloc] init]; - publishDict = [[NSMutableDictionary alloc] init]; + subscribeDict = [[NSMutableDictionary alloc] init]; + unsubscribeDict = [[NSMutableDictionary alloc] init]; + retrieveSubsDict = [[NSMutableDictionary alloc] init]; + configSubDict = [[NSMutableDictionary alloc] init]; + createDict = [[NSMutableDictionary alloc] init]; + deleteDict = [[NSMutableDictionary alloc] init]; + configNodeDict = [[NSMutableDictionary alloc] init]; + publishDict = [[NSMutableDictionary alloc] init]; + retrieveItemsDict = [[NSMutableDictionary alloc] init]; } return self; } @@ -100,14 +149,15 @@ - (BOOL)activate:(XMPPStream *)aXmppStream - (void)deactivate { - [subscribeDict removeAllObjects]; - [unsubscribeDict removeAllObjects]; - [retrieveDict removeAllObjects]; - [configSubDict removeAllObjects]; - [createDict removeAllObjects]; - [deleteDict removeAllObjects]; - [configNodeDict removeAllObjects]; - [publishDict removeAllObjects]; + [subscribeDict removeAllObjects]; + [unsubscribeDict removeAllObjects]; + [retrieveSubsDict removeAllObjects]; + [configSubDict removeAllObjects]; + [createDict removeAllObjects]; + [deleteDict removeAllObjects]; + [configNodeDict removeAllObjects]; + [publishDict removeAllObjects]; + [retrieveItemsDict removeAllObjects]; if (serviceJID == nil) { [[NSNotificationCenter defaultCenter] removeObserver:self name:XMPPStreamDidChangeMyJIDNotification object:nil]; @@ -124,9 +174,9 @@ - (void)myJIDDidChange:(NSNotification *)notification dispatch_block_t block = ^{ @autoreleasepool { - if (xmppStream == stream) + if (self->xmppStream == stream) { - myJID = xmppStream.myJID; + self->myJID = self->xmppStream.myJID; } }}; @@ -156,7 +206,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq NSString *elementID = [iq elementID]; NSString *node = nil; - if ((node = [subscribeDict objectForKey:elementID])) + if ((node = subscribeDict[elementID])) { // Example subscription success response: // @@ -199,7 +249,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [subscribeDict removeObjectForKey:elementID]; return YES; } - else if ((node = [unsubscribeDict objectForKey:elementID])) + else if ((node = unsubscribeDict[elementID])) { // Example unsubscribe success response: // @@ -230,7 +280,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [unsubscribeDict removeObjectForKey:elementID]; return YES; } - else if ((node = [retrieveDict objectForKey:elementID])) + else if ((node = retrieveSubsDict[elementID])) { // Example retrieve success response: // @@ -270,10 +320,10 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [multicastDelegate xmppPubSub:self didNotRetrieveSubscriptions:iq forNode:node]; } - [retrieveDict removeObjectForKey:elementID]; + [retrieveSubsDict removeObjectForKey:elementID]; return YES; } - else if ((node = [configSubDict objectForKey:elementID])) + else if ((node = configSubDict[elementID])) { // Example configure subscription success response: // @@ -296,7 +346,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [configSubDict removeObjectForKey:elementID]; return YES; } - else if ((node = [publishDict objectForKey:elementID])) + else if ((node = publishDict[elementID])) { // Example publish success response: // @@ -324,7 +374,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [publishDict removeObjectForKey:elementID]; return YES; } - else if ((node = [createDict objectForKey:elementID])) + else if ((node = createDict[elementID])) { // Example create success response: // @@ -347,7 +397,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [createDict removeObjectForKey:elementID]; return YES; } - else if ((node = [deleteDict objectForKey:elementID])) + else if ((node = deleteDict[elementID])) { // Example delete success response: // @@ -369,7 +419,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [deleteDict removeObjectForKey:elementID]; return YES; } - else if ((node = [configNodeDict objectForKey:elementID])) + else if ((node = configNodeDict[elementID])) { // Example configure node success response: // @@ -391,7 +441,37 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq [configNodeDict removeObjectForKey:elementID]; return YES; } - + else if ((node = retrieveItemsDict[elementID])) + { + // Example retrieve from node success response: + // + // + // + // + // + // ... + // + // + // + // + // + // Example delete error response: + // + // + // + // + // + // + // + + if ([[iq type] isEqualToString:@"result"]) + [multicastDelegate xmppPubSub:self didRetrieveItems:iq fromNode:node]; + else + [multicastDelegate xmppPubSub:self didNotRetrieveItems:iq fromNode:node]; + + [retrieveItemsDict removeObjectForKey:elementID]; + return YES; + } return NO; } @@ -400,41 +480,65 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq **/ - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message { + // + // + // + // + // [... entry ...] + // + // + // + // + + NSXMLElement *event = [message elementForName:@"event" xmlns:XMLNS_PUBSUB_EVENT]; + if (!event) return; + // Check to see if message is from our PubSub/PEP service if (serviceJID) { if (![serviceJID isEqualToJID:[message from]]) return; } else { - if ([myJID isEqualToJID:[message from] options:XMPPJIDCompareBare]) return; - } - - // - // - // - // - // [... entry ...] - // - // - // - // - - NSXMLElement *event = [message elementForName:@"event" xmlns:XMLNS_PUBSUB_EVENT]; - if (event) - { - [multicastDelegate xmppPubSub:self didReceiveMessage:message]; + if (self.pepPublisherJIDs) { + BOOL isFromPEPPublisher = NO; + for (XMPPJID *pepPublisherJID in self.pepPublisherJIDs) { + if ([pepPublisherJID isEqualToJID:[message from] options:XMPPJIDCompareBare]) { + isFromPEPPublisher = YES; + break; + } + } + if (!isFromPEPPublisher) return; + } + + if (self.pepNodes) { + for (NSString *eventType in @[@"collection", @"configuration", @"delete", @"items", @"purge", @"subscription"]) { + NSXMLElement *eventChildElement = [event elementForName:eventType]; + if (!eventChildElement) continue; + + NSString *node = [eventChildElement attributeStringValueForName:@"node"]; + if (!node || ![self.pepNodes containsObject:node]) return; + break; + } + } + + if (!self.pepPublisherJIDs && !self.pepNodes) { + if (![myJID isEqualToJID:[message from] options:XMPPJIDCompareBare]) return; + } } + + [multicastDelegate xmppPubSub:self didReceiveMessage:message]; } - (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error { - [subscribeDict removeAllObjects]; - [unsubscribeDict removeAllObjects]; - [retrieveDict removeAllObjects]; - [configSubDict removeAllObjects]; - [createDict removeAllObjects]; - [deleteDict removeAllObjects]; - [configNodeDict removeAllObjects]; - [publishDict removeAllObjects]; + [subscribeDict removeAllObjects]; + [unsubscribeDict removeAllObjects]; + [retrieveSubsDict removeAllObjects]; + [configSubDict removeAllObjects]; + [createDict removeAllObjects]; + [deleteDict removeAllObjects]; + [configNodeDict removeAllObjects]; + [publishDict removeAllObjects]; + [retrieveItemsDict removeAllObjects]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -521,7 +625,7 @@ - (NSString *)subscribeToNode:(NSString *)aNode withJID:(XMPPJID *)myBareOrFullJ // Generate uuid and add to dict NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ - [subscribeDict setObject:node forKey:uuid]; + self->subscribeDict[uuid] = node; }); // Example from XEP-0060 section 6.1.1: @@ -597,7 +701,7 @@ - (NSString *)unsubscribeFromNode:(NSString *)aNode withJID:(XMPPJID *)myBareOrF // Generate uuid and add to dict NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ - [unsubscribeDict setObject:node forKey:uuid]; + self->unsubscribeDict[uuid] = node; }); // Example from XEP-0060 section 6.2.1: @@ -640,9 +744,9 @@ - (NSString *)retrieveSubscriptionsForNode:(NSString *)aNode NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ if (node) - [retrieveDict setObject:node forKey:uuid]; + self->retrieveSubsDict[uuid] = node; else - [retrieveDict setObject:[NSNull null] forKey:uuid]; + self->retrieveSubsDict[uuid] = [NSNull null]; }); // Get subscriptions for all nodes: @@ -693,7 +797,7 @@ - (NSString *)configureSubscriptionToNode:(NSString *)aNode // Generate uuid and add to dict NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ - [configSubDict setObject:node forKey:uuid]; + self->configSubDict[uuid] = node; }); // Example from XEP-0060 section 6.3.5: @@ -758,7 +862,7 @@ - (NSString *)createNode:(NSString *)aNode withOptions:(NSDictionary *)options // Generate uuid and add to dict NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ - [createDict setObject:node forKey:uuid]; + self->createDict[uuid] = node; }); // @@ -818,7 +922,7 @@ - (NSString *)deleteNode:(NSString *)aNode // Generate uuid and add to dict NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ - [deleteDict setObject:node forKey:uuid]; + self->deleteDict[uuid] = node; }); // Example XEP-0060 section 8.4.1: @@ -857,7 +961,7 @@ - (NSString *)configureNode:(NSString *)aNode withOptions:(NSDictionary *)option // Generate uuid and add to dict NSString *uuid = [xmppStream generateUUID]; dispatch_async(moduleQueue, ^{ - [configNodeDict setObject:node forKey:uuid]; + self->configNodeDict[uuid] = node; }); // @@ -962,9 +1066,58 @@ - (NSString *)publishToNode:(NSString *)node [xmppStream sendElement:iq]; dispatch_async(moduleQueue, ^{ - [publishDict setObject:node forKey:uuid]; + self->publishDict[uuid] = node; }); return uuid; } +- (NSString *)retrieveItemsFromNode:(NSString *)node +{ + return [self retrieveItemsFromNode:node withItemIDs:nil]; +} + +- (NSString *)retrieveItemsFromNode:(NSString *)node withItemIDs:(NSArray *)itemIds +{ + if (node == nil) return nil; + + // + //   + //     + //       + //       + //     + //   + // + + NSString *uuid = [xmppStream generateUUID]; + + NSXMLElement *items = [NSXMLElement elementWithName:@"items"]; + [items addAttributeWithName:@"node" stringValue:node]; + + if (itemIds) { + for (id itemId in itemIds) + { + NSXMLElement *item = [NSXMLElement elementWithName:@"item"]; + [item addAttributeWithName:@"id" stringValue:itemId]; + [items addChild:item]; + } + } + + NSXMLElement *pubsub = [NSXMLElement elementWithName:@"pubsub" xmlns:XMLNS_PUBSUB]; + [pubsub addChild:items]; + + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:serviceJID elementID:uuid]; + [iq addChild:pubsub]; + + [xmppStream sendElement:iq]; + + dispatch_async(moduleQueue, ^{ + self->retrieveItemsDict[uuid] = node; + }); + return uuid; +} + @end diff --git a/Extensions/XEP-0065/TURNSocket.h b/Extensions/XEP-0065/TURNSocket.h index fc81f5f6c4..86f328e0a8 100644 --- a/Extensions/XEP-0065/TURNSocket.h +++ b/Extensions/XEP-0065/TURNSocket.h @@ -3,7 +3,8 @@ @class XMPPIQ; @class XMPPJID; @class XMPPStream; -@class GCDAsyncSocket; +@import CocoaAsyncSocket; +@protocol TURNSocketDelegate; /** * TURNSocket is an implementation of XEP-0065: SOCKS5 Bytestreams. @@ -11,53 +12,17 @@ * It is used for establishing an out-of-band bytestream between any two XMPP users, * mainly for the purpose of file transfer. **/ -@interface TURNSocket : NSObject -{ - int state; - BOOL isClient; - - dispatch_queue_t turnQueue; - void *turnQueueTag; - - XMPPStream *xmppStream; - XMPPJID *jid; - NSString *uuid; - - id delegate; - dispatch_queue_t delegateQueue; - - dispatch_source_t turnTimer; - - NSString *discoUUID; - dispatch_source_t discoTimer; - - NSArray *proxyCandidates; - NSUInteger proxyCandidateIndex; - - NSMutableArray *candidateJIDs; - NSUInteger candidateJIDIndex; - - NSMutableArray *streamhosts; - NSUInteger streamhostIndex; - - XMPPJID *proxyJID; - NSString *proxyHost; - UInt16 proxyPort; - - GCDAsyncSocket *asyncSocket; - - NSDate *startTime, *finishTime; -} +NS_ASSUME_NONNULL_BEGIN +@interface TURNSocket : NSObject + (BOOL)isNewStartTURNRequest:(XMPPIQ *)iq; -+ (NSArray *)proxyCandidates; -+ (void)setProxyCandidates:(NSArray *)candidates; +@property (class, atomic) NSArray *proxyCandidates; -- (id)initWithStream:(XMPPStream *)xmppStream toJID:(XMPPJID *)jid; -- (id)initWithStream:(XMPPStream *)xmppStream incomingTURNRequest:(XMPPIQ *)iq; +- (instancetype)initWithStream:(XMPPStream *)xmppStream toJID:(XMPPJID *)jid; +- (instancetype)initWithStream:(XMPPStream *)xmppStream incomingTURNRequest:(XMPPIQ *)iq; -- (void)startWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)aDelegateQueue; +- (void)startWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)aDelegateQueue; - (BOOL)isClient; @@ -78,3 +43,4 @@ @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0065/TURNSocket.m b/Extensions/XEP-0065/TURNSocket.m index a25c3f5f51..3899d73c9e 100644 --- a/Extensions/XEP-0065/TURNSocket.m +++ b/Extensions/XEP-0065/TURNSocket.m @@ -1,7 +1,7 @@ #import "TURNSocket.h" #import "XMPP.h" #import "XMPPLogging.h" -#import "GCDAsyncSocket.h" +@import CocoaAsyncSocket; #import "NSData+XMPP.h" #import "NSNumber+XMPP.h" @@ -43,6 +43,44 @@ #define TIMEOUT_READ 5.00 #define TIMEOUT_TOTAL 80.00 +@interface TURNSocket() { + int state; + BOOL isClient; + + dispatch_queue_t turnQueue; + void *turnQueueTag; + + XMPPStream *xmppStream; + XMPPJID *jid; + NSString *uuid; + + id delegate; + dispatch_queue_t delegateQueue; + + dispatch_source_t turnTimer; + + NSString *discoUUID; + dispatch_source_t discoTimer; + + NSArray *proxyCandidates; + NSUInteger proxyCandidateIndex; + + NSMutableArray *candidateJIDs; + NSUInteger candidateJIDIndex; + + NSMutableArray *streamhosts; + NSUInteger streamhostIndex; + + XMPPJID *proxyJID; + NSString *proxyHost; + UInt16 proxyPort; + + GCDAsyncSocket *asyncSocket; + + NSDate *startTime, *finishTime; +} +@end + // Declare private methods @interface TURNSocket (PrivateAPI) - (void)processDiscoItemsResponse:(XMPPIQ *)iq; @@ -92,7 +130,7 @@ + (void)initialize initialized = YES; existingTurnSockets = [[NSMutableDictionary alloc] init]; - proxyCandidates = [[NSMutableArray alloc] initWithObjects:@"jabber.org", nil]; + proxyCandidates = [@[@"jabber.org"] mutableCopy]; } } @@ -140,7 +178,7 @@ + (BOOL)isNewStartTURNRequest:(XMPPIQ *)iq @synchronized(existingTurnSockets) { - if ([existingTurnSockets objectForKey:uuid]) + if (existingTurnSockets[uuid]) return NO; else return YES; @@ -266,7 +304,7 @@ - (void)performPostInitSetup @synchronized(existingTurnSockets) { - [existingTurnSockets setObject:self forKey:uuid]; + existingTurnSockets[uuid] = self; } } @@ -327,7 +365,7 @@ - (void)startWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)aDelegat dispatch_async(turnQueue, ^{ @autoreleasepool { - if (state != STATE_INIT) + if (self->state != STATE_INIT) { XMPPLogWarn(@"%@: Ignoring start request. Turn procedure already started.", THIS_FILE); return; @@ -336,26 +374,26 @@ - (void)startWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)aDelegat // Set reference to delegate and delegate's queue. // Note that we do NOT retain the delegate. - delegate = aDelegate; - delegateQueue = aDelegateQueue; + self->delegate = aDelegate; + self->delegateQueue = aDelegateQueue; #if !OS_OBJECT_USE_OBJC dispatch_retain(delegateQueue); #endif // Add self as xmpp delegate so we'll get message responses - [xmppStream addDelegate:self delegateQueue:turnQueue]; + [self->xmppStream addDelegate:self delegateQueue:self->turnQueue]; // Start the timer to calculate how long the procedure takes - startTime = [[NSDate alloc] init]; + self->startTime = [[NSDate alloc] init]; // Schedule timer to cancel the turn procedure. // This ensures that, in the event of network error or crash, // the TURNSocket object won't remain in memory forever, and will eventually fail. - turnTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, turnQueue); + self->turnTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->turnQueue); - dispatch_source_set_event_handler(turnTimer, ^{ @autoreleasepool { + dispatch_source_set_event_handler(self->turnTimer, ^{ @autoreleasepool { [self doTotalTimeout]; @@ -363,12 +401,12 @@ - (void)startWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)aDelegat dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (TIMEOUT_TOTAL * NSEC_PER_SEC)); - dispatch_source_set_timer(turnTimer, tt, DISPATCH_TIME_FOREVER, 0.1); - dispatch_resume(turnTimer); + dispatch_source_set_timer(self->turnTimer, tt, DISPATCH_TIME_FOREVER, 0.1); + dispatch_resume(self->turnTimer); // Start the TURN procedure - if (isClient) + if (self->isClient) [self queryProxyCandidates]; else [self targetConnect]; @@ -395,12 +433,12 @@ - (void)abort { dispatch_block_t block = ^{ @autoreleasepool { - if ((state > STATE_INIT) && (state < STATE_DONE)) + if ((self->state > STATE_INIT) && (self->state < STATE_DONE)) { // The only thing we really have to do here is move the state to failure. // This simple act should prevent any further action from being taken in this TUNRSocket object, // since every action is dictated based on the current state. - state = STATE_FAILURE; + self->state = STATE_FAILURE; // And don't forget to cleanup after ourselves [self cleanup]; @@ -441,7 +479,7 @@ - (void)sendRequest NSUInteger i; for(i = 0; i < [streamhosts count]; i++) { - [query addChild:[streamhosts objectAtIndex:i]]; + [query addChild:streamhosts[i]]; } XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:jid elementID:uuid child:query]; @@ -603,7 +641,7 @@ - (void)processDiscoItemsResponse:(XMPPIQ *)iq NSUInteger i; for(i = 0; i < [items count]; i++) { - NSString *itemJidStr = [[[items objectAtIndex:i] attributeForName:@"jid"] stringValue]; + NSString *itemJidStr = [[items[i] attributeForName:@"jid"] stringValue]; XMPPJID *itemJid = [XMPPJID jidWithString:itemJidStr]; if(itemJid) @@ -636,7 +674,7 @@ - (void)processDiscoInfoResponse:(XMPPIQ *)iq NSUInteger i; for(i = 0; i < [identities count] && !found; i++) { - NSXMLElement *identity = [identities objectAtIndex:i]; + NSXMLElement *identity = identities[i]; NSString *category = [[identity attributeForName:@"category"] stringValue]; NSString *type = [[identity attributeForName:@"type"] stringValue]; @@ -663,7 +701,7 @@ - (void)processDiscoInfoResponse:(XMPPIQ *)iq // We could ignore the 404 error, and try to connect anyways, // but this would be useless because we'd be unable to activate the stream later. - XMPPJID *candidateJID = [candidateJIDs objectAtIndex:candidateJIDIndex]; + XMPPJID *candidateJID = candidateJIDs[candidateJIDIndex]; // So the service was not a useable proxy service, or will not allow us to use its proxy. // @@ -731,7 +769,7 @@ - (void)processRequestResponse:(XMPPIQ *)iq NSUInteger i; for(i = 0; i < [streamhosts count] && !found; i++) { - NSXMLElement *streamhost = [streamhosts objectAtIndex:i]; + NSXMLElement *streamhost = streamhosts[i]; NSString *streamhostJID = [[streamhost attributeForName:@"jid"] stringValue]; @@ -839,7 +877,7 @@ - (void)queryNextProxyCandidate { while ((proxyCandidateJID == nil) && (++proxyCandidateIndex < [proxyCandidates count])) { - NSString *proxyCandidate = [proxyCandidates objectAtIndex:proxyCandidateIndex]; + NSString *proxyCandidate = proxyCandidates[proxyCandidateIndex]; proxyCandidateJID = [XMPPJID jidWithString:proxyCandidate]; if (proxyCandidateJID == nil) @@ -896,7 +934,7 @@ - (void)queryCandidateJIDs NSUInteger i; for (i = 0; i < [candidateJIDs count]; i++) { - XMPPJID *candidateJID = [candidateJIDs objectAtIndex:i]; + XMPPJID *candidateJID = candidateJIDs[i]; NSRange proxyRange = [[candidateJID domain] rangeOfString:@"proxy" options:NSCaseInsensitiveSearch]; @@ -930,7 +968,7 @@ - (void)queryNextCandidateJID { [self updateDiscoUUID]; - XMPPJID *candidateJID = [candidateJIDs objectAtIndex:candidateJIDIndex]; + XMPPJID *candidateJID = candidateJIDs[candidateJIDIndex]; NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"http://jabber.org/protocol/disco#info"]; @@ -960,7 +998,7 @@ - (void)queryProxyAddress [self updateDiscoUUID]; - XMPPJID *candidateJID = [candidateJIDs objectAtIndex:candidateJIDIndex]; + XMPPJID *candidateJID = candidateJIDs[candidateJIDIndex]; NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"http://jabber.org/protocol/bytestreams"]; @@ -994,7 +1032,7 @@ - (void)targetNextConnect streamhostIndex++; if(streamhostIndex < [streamhosts count]) { - NSXMLElement *streamhost = [streamhosts objectAtIndex:streamhostIndex]; + NSXMLElement *streamhost = streamhosts[streamhostIndex]; proxyJID = [XMPPJID jidWithString:[[streamhost attributeForName:@"jid"] stringValue]]; @@ -1464,9 +1502,9 @@ - (void)succeed dispatch_async(delegateQueue, ^{ @autoreleasepool { - if ([delegate respondsToSelector:@selector(turnSocket:didSucceed:)]) + if ([self->delegate respondsToSelector:@selector(turnSocket:didSucceed:)]) { - [delegate turnSocket:self didSucceed:asyncSocket]; + [self->delegate turnSocket:self didSucceed:self->asyncSocket]; } }}); @@ -1487,9 +1525,9 @@ - (void)fail dispatch_async(delegateQueue, ^{ @autoreleasepool { - if ([delegate respondsToSelector:@selector(turnSocketDidFail:)]) + if ([self->delegate respondsToSelector:@selector(turnSocketDidFail:)]) { - [delegate turnSocketDidFail:self]; + [self->delegate turnSocketDidFail:self]; } }}); diff --git a/Extensions/XEP-0066/XMPPIQ+XEP_0066.h b/Extensions/XEP-0066/XMPPIQ+XEP_0066.h index 4c110ba21f..a3510ccea5 100644 --- a/Extensions/XEP-0066/XMPPIQ+XEP_0066.h +++ b/Extensions/XEP-0066/XMPPIQ+XEP_0066.h @@ -1,30 +1,31 @@ #import "XMPPIQ.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPIQ (XEP_0066) + (XMPPIQ *)outOfBandDataRequestTo:(XMPPJID *)jid elementID:(NSString *)eid URL:(NSURL *)URL - desc:(NSString *)dec; + desc:(nullable NSString *)dec; + (XMPPIQ *)outOfBandDataRequestTo:(XMPPJID *)jid elementID:(NSString *)eid URI:(NSString *)URI - desc:(NSString *)dec; + desc:(nullable NSString *)dec; -- (id)initOutOfBandDataRequestTo:(XMPPJID *)jid - elementID:(NSString *)eid - URL:(NSURL *)URL - desc:(NSString *)dec; +- (instancetype)initOutOfBandDataRequestTo:(XMPPJID *)jid + elementID:(NSString *)eid + URL:(NSURL *)URL + desc:(nullable NSString *)dec; -- (id)initOutOfBandDataRequestTo:(XMPPJID *)jid - elementID:(NSString *)eid - URI:(NSString *)URI - desc:(NSString *)dec; +- (instancetype)initOutOfBandDataRequestTo:(XMPPJID *)jid + elementID:(NSString *)eid + URI:(NSString *)URI + desc:(nullable NSString *)dec; -- (void)addOutOfBandURL:(NSURL *)URL desc:(NSString *)desc; -- (void)addOutOfBandURI:(NSString *)URI desc:(NSString *)desc; +- (void)addOutOfBandURL:(NSURL *)URL desc:(nullable NSString *)desc; +- (void)addOutOfBandURI:(NSString *)URI desc:(nullable NSString *)desc; - (XMPPIQ *)generateOutOfBandDataSuccessResponse; @@ -32,14 +33,15 @@ - (XMPPIQ *)generateOutOfBandDataRejectResponse; -- (BOOL)isOutOfBandDataRequest; -- (BOOL)isOutOfBandDataFailureResponse; -- (BOOL)isOutOfBandDataRejectResponse; +@property (nonatomic, readonly) BOOL isOutOfBandDataRequest; +@property (nonatomic, readonly) BOOL isOutOfBandDataFailureResponse; +@property (nonatomic, readonly) BOOL isOutOfBandDataRejectResponse; -- (BOOL)hasOutOfBandData; +@property (nonatomic, readonly) BOOL hasOutOfBandData; -- (NSURL *)outOfBandURL; -- (NSString *)outOfBandURI; -- (NSString *)outOfBandDesc; +@property (nonatomic, readonly, nullable) NSURL *outOfBandURL; +@property (nonatomic, readonly, nullable) NSString *outOfBandURI; +@property (nonatomic, readonly, nullable) NSString *outOfBandDesc; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0066/XMPPMessage+XEP_0066.h b/Extensions/XEP-0066/XMPPMessage+XEP_0066.h index 7cc0ab148b..a4a7545567 100644 --- a/Extensions/XEP-0066/XMPPMessage+XEP_0066.h +++ b/Extensions/XEP-0066/XMPPMessage+XEP_0066.h @@ -1,14 +1,16 @@ #import "XMPPMessage.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (XEP_0066) -- (void)addOutOfBandURL:(NSURL *)URL desc:(NSString *)desc; -- (void)addOutOfBandURI:(NSString *)URI desc:(NSString *)desc; +- (void)addOutOfBandURL:(NSURL *)URL desc:(nullable NSString *)desc; +- (void)addOutOfBandURI:(NSString *)URI desc:(nullable NSString *)desc; -- (BOOL)hasOutOfBandData; +@property (nonatomic, readonly) BOOL hasOutOfBandData; -- (NSURL *)outOfBandURL; -- (NSString *)outOfBandURI; -- (NSString *)outOfBandDesc; +@property (nonatomic, readonly, nullable) NSURL *outOfBandURL; +@property (nonatomic, readonly, nullable) NSString *outOfBandURI; +@property (nonatomic, readonly, nullable) NSString *outOfBandDesc; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0066/XMPPMessage+XEP_0066.m b/Extensions/XEP-0066/XMPPMessage+XEP_0066.m index dd9205612c..13e78db538 100644 --- a/Extensions/XEP-0066/XMPPMessage+XEP_0066.m +++ b/Extensions/XEP-0066/XMPPMessage+XEP_0066.m @@ -6,7 +6,7 @@ #endif #define NAME_OUT_OF_BAND @"x" -#define XMLNS_OUT_OF_BAND @"jabber:iq:oob" +#define XMLNS_OUT_OF_BAND @"jabber:x:oob" @implementation XMPPMessage (XEP_0066) diff --git a/Extensions/XEP-0066/XMPPOutOfBandResourceMessaging.h b/Extensions/XEP-0066/XMPPOutOfBandResourceMessaging.h new file mode 100644 index 0000000000..11a58cba3a --- /dev/null +++ b/Extensions/XEP-0066/XMPPOutOfBandResourceMessaging.h @@ -0,0 +1,28 @@ +#import "XMPPModule.h" + +@class XMPPMessage; + +NS_ASSUME_NONNULL_BEGIN + +/// A module that handles incoming XEP-0066 Out of Band Data URI messages. +@interface XMPPOutOfBandResourceMessaging : XMPPModule + +/// @brief The set of URL schemes handled by the module. +/// @discussion If set to @c nil (the default), URL filtering is disabled. +@property (copy, nullable) NSSet *relevantURLSchemes; + +@end + +/// A protocol defining @c XMPPOutOfBandResourceMessagingDelegate module delegate API. +@protocol XMPPOutOfBandResourceMessagingDelegate + +@optional + +/// Notifies the delegate that a message containing a relevant XEP-0066 Out of Band Data URI has been received in the stream. +- (void)xmppOutOfBandResourceMessaging:(XMPPOutOfBandResourceMessaging *)xmppOutOfBandResourceMessaging + didReceiveOutOfBandResourceMessage:(XMPPMessage *)message +NS_SWIFT_NAME(xmppOutOfBandResourceMessaging(_:didReceiveOutOfBandResourceMessage:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0066/XMPPOutOfBandResourceMessaging.m b/Extensions/XEP-0066/XMPPOutOfBandResourceMessaging.m new file mode 100644 index 0000000000..ddcd76b915 --- /dev/null +++ b/Extensions/XEP-0066/XMPPOutOfBandResourceMessaging.m @@ -0,0 +1,76 @@ +#import "XMPPOutOfBandResourceMessaging.h" +#import "XMPPMessage+XEP_0066.h" +#import "XMPPStream.h" +#import "XMPPLogging.h" + +// Log levels: off, error, warn, info, verbose +// Log flags: trace +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +@implementation XMPPOutOfBandResourceMessaging + +@synthesize relevantURLSchemes = _relevantURLSchemes; + +- (NSSet *)relevantURLSchemes +{ + __block NSSet *result; + dispatch_block_t block = ^{ + result = self->_relevantURLSchemes; + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + return result; +} + +- (void)setRelevantURLSchemes:(NSSet *)relevantURLSchemes +{ + NSSet *newValue = [relevantURLSchemes copy]; + dispatch_block_t block = ^{ + self->_relevantURLSchemes = newValue; + }; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)didActivate +{ + XMPPLogTrace(); +} + +- (void)willDeactivate +{ + XMPPLogTrace(); +} + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message +{ + XMPPLogTrace(); + + if (![message hasOutOfBandData]) { + return; + } + + NSString *resourceURIString = [message outOfBandURI]; + if (self.relevantURLSchemes) { + NSURL *resourceURL = [NSURL URLWithString:resourceURIString]; + if (!resourceURL.scheme || ![self.relevantURLSchemes containsObject:resourceURL.scheme]) { + return; + } + } + + XMPPLogInfo(@"Received out of band resource message"); + [multicastDelegate xmppOutOfBandResourceMessaging:self didReceiveOutOfBandResourceMessage:message]; +} + +@end diff --git a/Extensions/XEP-0077/XMPPRegistration.h b/Extensions/XEP-0077/XMPPRegistration.h index 94cc5ad544..6233b7d1b4 100644 --- a/Extensions/XEP-0077/XMPPRegistration.h +++ b/Extensions/XEP-0077/XMPPRegistration.h @@ -10,6 +10,7 @@ #define _XMPP_REGISTRATION_H +NS_ASSUME_NONNULL_BEGIN @interface XMPPRegistration : XMPPModule { XMPPIDTracker *xmppIDTracker; } @@ -33,7 +34,7 @@ * * @see cancelRegistration */ -- (BOOL)cancelRegistrationUsingPassword:(NSString *)password; +- (BOOL)cancelRegistrationUsingPassword:(nullable NSString *)password; /** * This method will attempt to cancel the current user's registration. The user *MUST* be @@ -70,7 +71,7 @@ * @param sender XMPPRegistration object invoking this delegate method. * @param error NSError containing more details of the failure. */ -- (void)passwordChangeFailed:(XMPPRegistration *)sender withError:(NSError *)error; +- (void)passwordChangeFailed:(XMPPRegistration *)sender withError:(nullable NSError *)error; /** * Implement this method when calling [regInstance cancelRegistration] or a variation. It @@ -89,6 +90,8 @@ * @param sender XMPPRegistration object invoking this delegate method. * @param error NSError containing more details of the failure. */ -- (void)cancelRegistrationFailed:(XMPPRegistration *)sender withError:(NSError *)error; +- (void)cancelRegistrationFailed:(XMPPRegistration *)sender withError:(nullable NSError *)error; @end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0077/XMPPRegistration.m b/Extensions/XEP-0077/XMPPRegistration.m index 4d65f1d733..9367a75a65 100644 --- a/Extensions/XEP-0077/XMPPRegistration.m +++ b/Extensions/XEP-0077/XMPPRegistration.m @@ -50,11 +50,11 @@ - (BOOL)changePassword:(NSString *)newPassword dispatch_block_t block = ^{ @autoreleasepool { - NSString *toStr = xmppStream.myJID.domain; + NSString *toStr = self->xmppStream.myJID.domain; NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:register"]; NSXMLElement *username = [NSXMLElement elementWithName:@"username" - stringValue:xmppStream.myJID.user]; + stringValue:self->xmppStream.myJID.user]; NSXMLElement *password = [NSXMLElement elementWithName:@"password" stringValue:newPassword]; [query addChild:username]; @@ -62,15 +62,15 @@ - (BOOL)changePassword:(NSString *)newPassword XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:[XMPPJID jidWithString:toStr] - elementID:[xmppStream generateUUID] + elementID:[self->xmppStream generateUUID] child:query]; - [xmppIDTracker addID:[iq elementID] + [self->xmppIDTracker addID:[iq elementID] target:self selector:@selector(handlePasswordChangeQueryIQ:withInfo:) timeout:60]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; } }; @@ -113,15 +113,15 @@ - (BOOL)cancelRegistrationUsingPassword:(NSString *)password NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:register"]; [query addChild:remove]; XMPPIQ *iq = [XMPPIQ iqWithType:@"set" - elementID:[xmppStream generateUUID] + elementID:[self->xmppStream generateUUID] child:query]; - [xmppIDTracker addElement:iq + [self->xmppIDTracker addElement:iq target:self selector:@selector(handleRegistrationCancelQueryIQ:withInfo:) timeout:60]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; } }; @@ -155,19 +155,19 @@ - (void)handlePasswordChangeQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo code:errCode userInfo:errInfo]; - [multicastDelegate passwordChangeFailed:self + [self->multicastDelegate passwordChangeFailed:self withError:err]; return; } NSString *type = [iq type]; - if ([type isEqualToString:@"result"] && iq.childCount == 0) { - [multicastDelegate passwordChangeSuccessful:self]; + if ([type isEqualToString:@"result"]) { + [self->multicastDelegate passwordChangeSuccessful:self]; } else { // this should be impossible to reach, but just for safety's sake... - [multicastDelegate passwordChangeFailed:self - withError:nil]; + [self->multicastDelegate passwordChangeFailed:self + withError:nil]; } } }; @@ -196,19 +196,19 @@ - (void)handleRegistrationCancelQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTracking code:errCode userInfo:errInfo]; - [multicastDelegate cancelRegistrationFailed:self - withError:err]; + [self->multicastDelegate cancelRegistrationFailed:self + withError:err]; return; } NSString *type = [iq type]; - if ([type isEqualToString:@"result"] && iq.childCount == 0) { - [multicastDelegate cancelRegistrationSuccessful:self]; + if ([type isEqualToString:@"result"]) { + [self->multicastDelegate cancelRegistrationSuccessful:self]; } else { // this should be impossible to reach, but just for safety's sake... - [multicastDelegate cancelRegistrationFailed:self - withError:nil]; + [self->multicastDelegate cancelRegistrationFailed:self + withError:nil]; } } }; @@ -228,11 +228,10 @@ - (BOOL)xmppStream:(XMPPStream *)stream didReceiveIQ:(XMPPIQ *)iq NSString *type = [iq type]; if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]) { - NSLog(@"invoking with iq: %@", iq); return [xmppIDTracker invokeForElement:iq withObject:iq]; } return NO; } -@end \ No newline at end of file +@end diff --git a/Extensions/XEP-0082/NSDate+XMPPDateTimeProfiles.h b/Extensions/XEP-0082/NSDate+XMPPDateTimeProfiles.h index bc142d58a2..004ad4ae24 100644 --- a/Extensions/XEP-0082/NSDate+XMPPDateTimeProfiles.h +++ b/Extensions/XEP-0082/NSDate+XMPPDateTimeProfiles.h @@ -10,17 +10,16 @@ #import +NS_ASSUME_NONNULL_BEGIN @interface NSDate(XMPPDateTimeProfiles) ++ (nullable NSDate *)dateWithXmppDateString:(NSString *)str; ++ (nullable NSDate *)dateWithXmppTimeString:(NSString *)str; ++ (nullable NSDate *)dateWithXmppDateTimeString:(NSString *)str; -+ (NSDate *)dateWithXmppDateString:(NSString *)str; -+ (NSDate *)dateWithXmppTimeString:(NSString *)str; -+ (NSDate *)dateWithXmppDateTimeString:(NSString *)str; - - -- (NSString *)xmppDateString; -- (NSString *)xmppTimeString; -- (NSString *)xmppDateTimeString; - +@property (nonatomic, readonly) NSString *xmppDateString; +@property (nonatomic, readonly) NSString *xmppTimeString; +@property (nonatomic, readonly) NSString *xmppDateTimeString; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0082/XMPPDateTimeProfiles.h b/Extensions/XEP-0082/XMPPDateTimeProfiles.h index 5a717e0042..e245a3850a 100644 --- a/Extensions/XEP-0082/XMPPDateTimeProfiles.h +++ b/Extensions/XEP-0082/XMPPDateTimeProfiles.h @@ -1,6 +1,7 @@ #import #import "NSDate+XMPPDateTimeProfiles.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPDateTimeProfiles : NSObject /** @@ -8,10 +9,11 @@ * They return nil if the given string doesn't follow the spec. **/ -+ (NSDate *)parseDate:(NSString *)dateStr; -+ (NSDate *)parseTime:(NSString *)timeStr; -+ (NSDate *)parseDateTime:(NSString *)dateTimeStr; ++ (nullable NSDate *)parseDate:(NSString *)dateStr; ++ (nullable NSDate *)parseTime:(NSString *)timeStr; ++ (nullable NSDate *)parseDateTime:(NSString *)dateTimeStr; -+ (NSTimeZone *)parseTimeZoneOffset:(NSString *)tzo; ++ (nullable NSTimeZone *)parseTimeZoneOffset:(NSString *)tzo; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0085/XMPPMessage+XEP_0085.h b/Extensions/XEP-0085/XMPPMessage+XEP_0085.h index 481c39542a..fa63698c4c 100644 --- a/Extensions/XEP-0085/XMPPMessage+XEP_0085.h +++ b/Extensions/XEP-0085/XMPPMessage+XEP_0085.h @@ -1,18 +1,22 @@ #import #import "XMPPMessage.h" +NS_ASSUME_NONNULL_BEGIN +/** XEP-0085: Chat States XMLNS "http://jabber.org/protocol/chatstates" */ +extern NSString *const ChatStatesXmlns; @interface XMPPMessage (XEP_0085) -- (NSString *)chatState; +@property (nonatomic, readonly, nullable) NSString *chatStateValue; +@property (nonatomic, readonly, nullable) NSString *chatState NS_REFINED_FOR_SWIFT DEPRECATED_MSG_ATTRIBUTE("Use chatStateValue to access the raw String value. This property will be removed in a future release."); -- (BOOL)hasChatState; +@property (nonatomic, readonly) BOOL hasChatState; -- (BOOL)hasActiveChatState; -- (BOOL)hasComposingChatState; -- (BOOL)hasPausedChatState; -- (BOOL)hasInactiveChatState; -- (BOOL)hasGoneChatState; +@property (nonatomic, readonly) BOOL hasActiveChatState; +@property (nonatomic, readonly) BOOL hasComposingChatState; +@property (nonatomic, readonly) BOOL hasPausedChatState; +@property (nonatomic, readonly) BOOL hasInactiveChatState; +@property (nonatomic, readonly) BOOL hasGoneChatState; - (void)addActiveChatState; - (void)addComposingChatState; @@ -21,3 +25,4 @@ - (void)addGoneChatState; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0085/XMPPMessage+XEP_0085.m b/Extensions/XEP-0085/XMPPMessage+XEP_0085.m index d44dbc427a..6d8763630e 100644 --- a/Extensions/XEP-0085/XMPPMessage+XEP_0085.m +++ b/Extensions/XEP-0085/XMPPMessage+XEP_0085.m @@ -1,69 +1,72 @@ #import "XMPPMessage+XEP_0085.h" #import "NSXMLElement+XMPP.h" - -static NSString *const xmlns_chatstates = @"http://jabber.org/protocol/chatstates"; +NSString *const ChatStatesXmlns = @"http://jabber.org/protocol/chatstates"; @implementation XMPPMessage (XEP_0085) -- (NSString *)chatState{ - return [[[self elementsForXmlns:xmlns_chatstates] lastObject] name]; +- (NSString*)chatState { + return self.chatState; +} + +- (NSString *)chatStateValue{ + return [[[self elementsForXmlns:ChatStatesXmlns] lastObject] name]; } - (BOOL)hasChatState { - return ([[self elementsForXmlns:xmlns_chatstates] count] > 0); + return ([[self elementsForXmlns:ChatStatesXmlns] count] > 0); } - (BOOL)hasActiveChatState { - return ([self elementForName:@"active" xmlns:xmlns_chatstates] != nil); + return ([self elementForName:@"active" xmlns:ChatStatesXmlns] != nil); } - (BOOL)hasComposingChatState { - return ([self elementForName:@"composing" xmlns:xmlns_chatstates] != nil); + return ([self elementForName:@"composing" xmlns:ChatStatesXmlns] != nil); } - (BOOL)hasPausedChatState { - return ([self elementForName:@"paused" xmlns:xmlns_chatstates] != nil); + return ([self elementForName:@"paused" xmlns:ChatStatesXmlns] != nil); } - (BOOL)hasInactiveChatState { - return ([self elementForName:@"inactive" xmlns:xmlns_chatstates] != nil); + return ([self elementForName:@"inactive" xmlns:ChatStatesXmlns] != nil); } - (BOOL)hasGoneChatState { - return ([self elementForName:@"gone" xmlns:xmlns_chatstates] != nil); + return ([self elementForName:@"gone" xmlns:ChatStatesXmlns] != nil); } - (void)addActiveChatState { - [self addChild:[NSXMLElement elementWithName:@"active" xmlns:xmlns_chatstates]]; + [self addChild:[NSXMLElement elementWithName:@"active" xmlns:ChatStatesXmlns]]; } - (void)addComposingChatState { - [self addChild:[NSXMLElement elementWithName:@"composing" xmlns:xmlns_chatstates]]; + [self addChild:[NSXMLElement elementWithName:@"composing" xmlns:ChatStatesXmlns]]; } - (void)addPausedChatState { - [self addChild:[NSXMLElement elementWithName:@"paused" xmlns:xmlns_chatstates]]; + [self addChild:[NSXMLElement elementWithName:@"paused" xmlns:ChatStatesXmlns]]; } - (void)addInactiveChatState { - [self addChild:[NSXMLElement elementWithName:@"inactive" xmlns:xmlns_chatstates]]; + [self addChild:[NSXMLElement elementWithName:@"inactive" xmlns:ChatStatesXmlns]]; } - (void)addGoneChatState { - [self addChild:[NSXMLElement elementWithName:@"gone" xmlns:xmlns_chatstates]]; + [self addChild:[NSXMLElement elementWithName:@"gone" xmlns:ChatStatesXmlns]]; } @end diff --git a/Extensions/XEP-0092/XMPPSoftwareVersion.h b/Extensions/XEP-0092/XMPPSoftwareVersion.h index 216f407fc6..52abe3216d 100644 --- a/Extensions/XEP-0092/XMPPSoftwareVersion.h +++ b/Extensions/XEP-0092/XMPPSoftwareVersion.h @@ -1,16 +1,24 @@ +#import + +#if TARGET_OS_IPHONE + #import +#else + #import +#endif + #import "XMPPModule.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPSoftwareVersion : XMPPModule @property (copy,readonly) NSString *name; @property (copy,readonly) NSString *version; @property (copy,readonly) NSString *os; -- (id)initWithDispatchQueue:(dispatch_queue_t)queue; - -- (id)initWithName:(NSString *)name - version:(NSString *)version - os:(NSString *)os - dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)initWithName:(NSString *)name + version:(NSString *)version + os:(NSString *)os + dispatchQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0092/XMPPSoftwareVersion.m b/Extensions/XEP-0092/XMPPSoftwareVersion.m index 20dcda4317..0c41e0b9e9 100644 --- a/Extensions/XEP-0092/XMPPSoftwareVersion.m +++ b/Extensions/XEP-0092/XMPPSoftwareVersion.m @@ -6,7 +6,7 @@ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif -#define XMLNS_URN_XMPP_VERSION @"urn:xmpp:jabber:iq:version" +#define XMLNS_URN_XMPP_VERSION @"jabber:iq:version" @implementation XMPPSoftwareVersion diff --git a/Extensions/XEP-0100/XMPPTransports.h b/Extensions/XEP-0100/XMPPTransports.h index ffeee1cd96..9b9655e38f 100644 --- a/Extensions/XEP-0100/XMPPTransports.h +++ b/Extensions/XEP-0100/XMPPTransports.h @@ -4,13 +4,10 @@ @class XMPPStream; - +NS_ASSUME_NONNULL_BEGIN @interface XMPPTransports : NSObject -{ - XMPPStream *xmppStream; -} -- (id)initWithStream:(XMPPStream *)xmppStream; +- (instancetype)initWithStream:(XMPPStream *)xmppStream; @property (nonatomic, strong, readonly) XMPPStream *xmppStream; @@ -21,3 +18,4 @@ - (void)unregisterLegacyService:(NSString *)service; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0106/NSString+XEP_0106.h b/Extensions/XEP-0106/NSString+XEP_0106.h index c9a01af76d..bbdd0f8c4f 100644 --- a/Extensions/XEP-0106/NSString+XEP_0106.h +++ b/Extensions/XEP-0106/NSString+XEP_0106.h @@ -2,8 +2,8 @@ @interface NSString (XEP_0106) -- (NSString *)jidEscapedString; +@property (nonatomic, readonly, nullable) NSString * jidEscapedString; -- (NSString *)jidUnescapedString; +@property (nonatomic, readonly, nullable) NSString * jidUnescapedString; @end diff --git a/Extensions/XEP-0115/CoreDataStorage/XMPPCapabilitiesCoreDataStorage.m b/Extensions/XEP-0115/CoreDataStorage/XMPPCapabilitiesCoreDataStorage.m index 3ebdd2e448..3b5f5123a1 100644 --- a/Extensions/XEP-0115/CoreDataStorage/XMPPCapabilitiesCoreDataStorage.m +++ b/Extensions/XEP-0115/CoreDataStorage/XMPPCapabilitiesCoreDataStorage.m @@ -476,7 +476,7 @@ - (void)setCapabilities:(NSXMLElement *)capabilities forHash:(NSString *)hash al NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:entity]; [fetchRequest setPredicate:predicate]; - [fetchRequest setFetchBatchSize:saveThreshold]; + [fetchRequest setFetchBatchSize:self->saveThreshold]; NSArray *results = [[self managedObjectContext] executeFetchRequest:fetchRequest error:nil]; @@ -486,7 +486,7 @@ - (void)setCapabilities:(NSXMLElement *)capabilities forHash:(NSString *)hash al { resource.caps = caps; - if (++unsavedCount >= saveThreshold) + if (++unsavedCount >= self->saveThreshold) { [self save]; } diff --git a/Extensions/XEP-0115/CoreDataStorage/XMPPCapsCoreDataStorageObject.h b/Extensions/XEP-0115/CoreDataStorage/XMPPCapsCoreDataStorageObject.h index 4e7257595a..9e41dd30b3 100644 --- a/Extensions/XEP-0115/CoreDataStorage/XMPPCapsCoreDataStorageObject.h +++ b/Extensions/XEP-0115/CoreDataStorage/XMPPCapsCoreDataStorageObject.h @@ -1,8 +1,6 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; @class XMPPCapsResourceCoreDataStorageObject; diff --git a/Extensions/XEP-0115/CoreDataStorage/XMPPCapsResourceCoreDataStorageObject.m b/Extensions/XEP-0115/CoreDataStorage/XMPPCapsResourceCoreDataStorageObject.m index 28d1cc9bb3..183345d308 100644 --- a/Extensions/XEP-0115/CoreDataStorage/XMPPCapsResourceCoreDataStorageObject.m +++ b/Extensions/XEP-0115/CoreDataStorage/XMPPCapsResourceCoreDataStorageObject.m @@ -30,7 +30,7 @@ - (BOOL)haveFailed - (void)setHaveFailed:(BOOL)flag { - self.failed = [NSNumber numberWithBool:flag]; + self.failed = @(flag); } @end diff --git a/Extensions/XEP-0115/XMPPCapabilities.h b/Extensions/XEP-0115/XMPPCapabilities.h index 1be070afe4..d46c2ea37d 100644 --- a/Extensions/XEP-0115/XMPPCapabilities.h +++ b/Extensions/XEP-0115/XMPPCapabilities.h @@ -4,6 +4,7 @@ #define _XMPP_CAPABILITIES_H @protocol XMPPCapabilitiesStorage; +@class GCDTimerWrapper; /** * This class provides support for capabilities discovery. @@ -16,6 +17,7 @@ * provides a mechanism to persistently store XEP-0115 hased caps, * and makes available a simple API to query (disco#info) a resource or server. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPCapabilities : XMPPModule { __strong id xmppCapabilitiesStorage; @@ -28,7 +30,7 @@ NSMutableSet *discoRequestJidSet; NSMutableDictionary *discoRequestHashDict; - NSMutableDictionary *discoTimerJidDict; + NSMutableDictionary *discoTimerJidDict; BOOL autoFetchHashedCapabilities; BOOL autoFetchNonHashedCapabilities; @@ -39,8 +41,11 @@ NSMutableSet *timers; } -- (id)initWithCapabilitiesStorage:(id )storage; -- (id)initWithCapabilitiesStorage:(id )storage dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; + +- (instancetype)initWithCapabilitiesStorage:(id )storage; +- (instancetype)initWithCapabilitiesStorage:(id )storage dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; @property (nonatomic, strong, readonly) id xmppCapabilitiesStorage; @@ -142,7 +147,7 @@ * If given, the jid must have been registered via the given stream. * Otherwise it will match the given jid from any stream this storage instance is managing. **/ -- (BOOL)areCapabilitiesKnownForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; +- (BOOL)areCapabilitiesKnownForJID:(nullable XMPPJID *)jid xmppStream:(nullable XMPPStream *)stream; /** * Returns the capabilities for the given jid. @@ -152,7 +157,7 @@ * If given, the jid must have been registered via the given stream. * Otherwise it will match the given jid from any stream this storage instance is managing. **/ -- (NSXMLElement *)capabilitiesForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; +- (nullable NSXMLElement *)capabilitiesForJID:(nullable XMPPJID *)jid xmppStream:(nullable XMPPStream *)stream; /** * Returns the capabilities for the given jid. @@ -178,7 +183,7 @@ * If given, the jid must have been registered via the given stream. * Otherwise it will match the given jid from any stream this storage instance is managing. **/ -- (NSXMLElement *)capabilitiesForJID:(XMPPJID *)jid ext:(NSString **)extPtr xmppStream:(XMPPStream *)stream; +- (nullable NSXMLElement *)capabilitiesForJID:(nullable XMPPJID *)jid ext:(NSString * _Nullable * _Nullable)extPtr xmppStream:(nullable XMPPStream *)stream; // // @@ -232,12 +237,12 @@ **/ - (BOOL)setCapabilitiesNode:(NSString *)node ver:(NSString *)ver - ext:(NSString *)ext - hash:(NSString *)hash - algorithm:(NSString *)hashAlg + ext:(nullable NSString *)ext + hash:(nullable NSString *)hash + algorithm:(nullable NSString *)hashAlg forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream - andGetNewCapabilities:(NSXMLElement **)newCapabilitiesPtr; + andGetNewCapabilities:(NSXMLElement *_Nullable*_Nullable)newCapabilitiesPtr; /** * Fetches the associated capabilities hash for a given jid. @@ -245,8 +250,8 @@ * If the jid is not associated with a capabilities hash, this method should return NO. * Otherwise it should return YES, and set the corresponding variables. **/ -- (BOOL)getCapabilitiesHash:(NSString **)hashPtr - algorithm:(NSString **)hashAlgPtr +- (BOOL)getCapabilitiesHash:(NSString *_Nullable*_Nullable)hashPtr + algorithm:(NSString *_Nullable*_Nullable)hashAlgPtr forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; @@ -264,13 +269,13 @@ * * If the capabilities are known, the areCapabilitiesKnown boolean should be set to YES. **/ -- (void)getCapabilitiesKnown:(BOOL *)areCapabilitiesKnownPtr - failed:(BOOL *)haveFailedFetchingBeforePtr - node:(NSString **)nodePtr - ver:(NSString **)verPtr - ext:(NSString **)extPtr - hash:(NSString **)hashPtr - algorithm:(NSString **)hashAlgPtr +- (void)getCapabilitiesKnown:(BOOL * _Nullable )areCapabilitiesKnownPtr + failed:(BOOL * _Nullable)haveFailedFetchingBeforePtr + node:(NSString *_Nullable*_Nullable)nodePtr + ver:(NSString *_Nullable*_Nullable)verPtr + ext:(NSString *_Nullable*_Nullable)extPtr + hash:(NSString *_Nullable*_Nullable)hashPtr + algorithm:(NSString *_Nullable*_Nullable)hashAlgPtr forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; @@ -368,7 +373,7 @@ * Duplicate features are automatically discarded * For more control over your capablities use xmppCapabilities:collectingMyCapabilities: **/ -- (NSArray *)myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender; +- (NSArray*)myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender; /** * Invoked when capabilities have been discovered for an available JID. @@ -378,3 +383,5 @@ - (void)xmppCapabilities:(XMPPCapabilities *)sender didDiscoverCapabilities:(NSXMLElement *)caps forJID:(XMPPJID *)jid; @end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0115/XMPPCapabilities.m b/Extensions/XEP-0115/XMPPCapabilities.m index d2dd25e6e3..6216cff17d 100644 --- a/Extensions/XEP-0115/XMPPCapabilities.m +++ b/Extensions/XEP-0115/XMPPCapabilities.m @@ -83,22 +83,6 @@ @implementation XMPPCapabilities @dynamic autoFetchNonHashedCapabilities; @dynamic autoFetchMyServerCapabilities; -- (id)init -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPCapabilities.h are supported. - - return [self initWithCapabilitiesStorage:nil dispatchQueue:NULL]; -} - -- (id)initWithDispatchQueue:(dispatch_queue_t)queue -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPCapabilities.h are supported. - - return [self initWithCapabilitiesStorage:nil dispatchQueue:queue]; -} - - (id)initWithCapabilitiesStorage:(id )storage { return [self initWithCapabilitiesStorage:storage dispatchQueue:NULL]; @@ -152,12 +136,10 @@ - (id)initWithCapabilitiesStorage:(id )storage dispatch } - (void)dealloc -{ - for (GCDTimerWrapper *timerWrapper in discoTimerJidDict) - { - [timerWrapper cancel]; - } - +{ + [discoTimerJidDict enumerateKeysAndObjectsUsingBlock:^(XMPPJID * _Nonnull key, GCDTimerWrapper * _Nonnull obj, BOOL * _Nonnull stop) { + [obj cancel]; + }]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -180,7 +162,7 @@ - (NSString *)myCapabilitiesNode __block NSString *result; dispatch_sync(moduleQueue, ^{ - result = myCapabilitiesNode; + result = self->myCapabilitiesNode; }); return result; @@ -192,7 +174,7 @@ - (void)setMyCapabilitiesNode:(NSString *)flag NSAssert([flag length], @"myCapabilitiesNode MUST NOT be nil"); dispatch_block_t block = ^{ - myCapabilitiesNode = flag; + self->myCapabilitiesNode = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -206,7 +188,7 @@ - (BOOL)autoFetchHashedCapabilities __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoFetchHashedCapabilities; + result = self->autoFetchHashedCapabilities; }; if (dispatch_get_specific(moduleQueueTag)) @@ -220,7 +202,7 @@ - (BOOL)autoFetchHashedCapabilities - (void)setAutoFetchHashedCapabilities:(BOOL)flag { dispatch_block_t block = ^{ - autoFetchHashedCapabilities = flag; + self->autoFetchHashedCapabilities = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -234,7 +216,7 @@ - (BOOL)autoFetchNonHashedCapabilities __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoFetchNonHashedCapabilities; + result = self->autoFetchNonHashedCapabilities; }; if (dispatch_get_specific(moduleQueueTag)) @@ -248,7 +230,7 @@ - (BOOL)autoFetchNonHashedCapabilities - (void)setAutoFetchNonHashedCapabilities:(BOOL)flag { dispatch_block_t block = ^{ - autoFetchNonHashedCapabilities = flag; + self->autoFetchNonHashedCapabilities = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -262,7 +244,7 @@ - (BOOL)autoFetchMyServerCapabilities __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoFetchMyServerCapabilities; + result = self->autoFetchMyServerCapabilities; }; if (dispatch_get_specific(moduleQueueTag)) @@ -276,7 +258,7 @@ - (BOOL)autoFetchMyServerCapabilities - (void)setAutoFetchMyServerCapabilities:(BOOL)flag { dispatch_block_t block = ^{ - autoFetchMyServerCapabilities = flag; + self->autoFetchMyServerCapabilities = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -409,12 +391,12 @@ static NSInteger sortFeatures(NSXMLElement *feature1, NSXMLElement *feature2, vo { if ([values count] > 1) { - NSString *baseValue = [[values objectAtIndex:0] stringValue]; + NSString *baseValue = [values[0] stringValue]; NSUInteger i; for (i = 1; i < [values count]; i++) { - NSString *value = [[values objectAtIndex:i] stringValue]; + NSString *value = [values[i] stringValue]; if (![value isEqualToString:baseValue]) { @@ -496,7 +478,7 @@ static NSInteger sortFieldValues(NSXMLElement *value1, NSXMLElement *value2, voi return [str1 compare:str2 options:NSLiteralSearch]; } -- (NSString *)hashCapabilitiesFromQuery:(NSXMLElement *)query ++ (NSString *)hashCapabilitiesFromQuery:(NSXMLElement *)query { if (query == nil) return nil; @@ -773,7 +755,7 @@ - (void)collectMyCapabilities }}); } - dispatch_async(moduleQueue, ^{ @autoreleasepool { + dispatch_async(self->moduleQueue, ^{ @autoreleasepool { [self continueCollectMyCapabilities:query]; }}); @@ -796,7 +778,7 @@ - (void)continueCollectMyCapabilities:(NSXMLElement *)query XMPPLogVerbose(@"%@: My capabilities:\n%@", THIS_FILE, [query XMLStringWithOptions:(NSXMLNodeCompactEmptyElement | NSXMLNodePrettyPrint)]); - NSString *hash = [self hashCapabilitiesFromQuery:query]; + NSString *hash = [self.class hashCapabilitiesFromQuery:query]; if (hash == nil) { @@ -878,7 +860,7 @@ - (void)fetchCapabilitiesForJID:(XMPPJID *)jid dispatch_block_t block = ^{ @autoreleasepool { - if ([discoRequestJidSet containsObject:jid]) + if ([self->discoRequestJidSet containsObject:jid]) { // We're already requesting capabilities concerning this JID return; @@ -892,7 +874,7 @@ - (void)fetchCapabilitiesForJID:(XMPPJID *)jid NSString *hash = nil; NSString *hashAlg = nil; - [xmppCapabilitiesStorage getCapabilitiesKnown:&areCapabilitiesKnown + [self->xmppCapabilitiesStorage getCapabilitiesKnown:&areCapabilitiesKnown failed:&haveFailedFetchingBefore node:&node ver:&ver @@ -900,7 +882,7 @@ - (void)fetchCapabilitiesForJID:(XMPPJID *)jid hash:&hash algorithm:&hashAlg forJID:jid - xmppStream:xmppStream]; + xmppStream:self->xmppStream]; if (areCapabilitiesKnown) { @@ -931,7 +913,7 @@ - (void)fetchCapabilitiesForJID:(XMPPJID *)jid // However, there is still a disco request that concerns the jid. key = [self keyFromHash:hash algorithm:hashAlg]; - NSMutableArray *jids = [discoRequestHashDict objectForKey:key]; + NSMutableArray *jids = self->discoRequestHashDict[key]; if (jids) { @@ -939,7 +921,7 @@ - (void)fetchCapabilitiesForJID:(XMPPJID *)jid // That is, there is another JID with the same hash, and we've already sent a disco request to it. [jids addObject:jid]; - [discoRequestJidSet addObject:jid]; + [self->discoRequestJidSet addObject:jid]; return; } @@ -947,15 +929,15 @@ - (void)fetchCapabilitiesForJID:(XMPPJID *)jid // The first object in the jids array is the index of the last jid that we've sent a disco request to. // This is used in case the jid does not respond. - NSNumber *requestIndexNum = [NSNumber numberWithUnsignedInteger:1]; - jids = [NSMutableArray arrayWithObjects:requestIndexNum, jid, nil]; + NSNumber *requestIndexNum = @1; + jids = [@[requestIndexNum, jid] mutableCopy]; - [discoRequestHashDict setObject:jids forKey:key]; - [discoRequestJidSet addObject:jid]; + self->discoRequestHashDict[key] = jids; + [self->discoRequestJidSet addObject:jid]; } else { - [discoRequestJidSet addObject:jid]; + [self->discoRequestJidSet addObject:jid]; } // Send disco#info query @@ -1057,7 +1039,7 @@ - (void)handlePresenceCapabilities:(NSXMLElement *)c fromJID:(XMPPJID *)jid // Are we already fetching the capabilities? NSString *key = [self keyFromHash:ver algorithm:hash]; - NSMutableArray *jids = [discoRequestHashDict objectForKey:key]; + NSMutableArray *jids = discoRequestHashDict[key]; if (jids) { @@ -1098,10 +1080,10 @@ - (void)handlePresenceCapabilities:(NSXMLElement *)c fromJID:(XMPPJID *)jid // So how do we know what the next jid in the list is? // Via the requestIndexNum of course. - NSNumber *requestIndexNum = [NSNumber numberWithUnsignedInteger:1]; - jids = [NSMutableArray arrayWithObjects:requestIndexNum, jid, nil]; + NSNumber *requestIndexNum = @1; + jids = [@[requestIndexNum, jid] mutableCopy]; - [discoRequestHashDict setObject:jids forKey:key]; + discoRequestHashDict[key] = jids; [discoRequestJidSet addObject:jid]; // Send disco#info query @@ -1258,7 +1240,7 @@ - (void)handleDiscoResponse:(NSXMLElement *)querySubElement fromJID:(XMPPJID *)j NSString *key = [self keyFromHash:hash algorithm:hashAlg]; - NSString *calculatedHash = [self hashCapabilitiesFromQuery:query]; + NSString *calculatedHash = [self.class hashCapabilitiesFromQuery:query]; if ([calculatedHash isEqualToString:hash]) { @@ -1268,12 +1250,12 @@ - (void)handleDiscoResponse:(NSXMLElement *)querySubElement fromJID:(XMPPJID *)j [xmppCapabilitiesStorage setCapabilities:query forHash:hash algorithm:hashAlg]; // Remove the jid(s) from the discoRequest variables - NSArray *jids = [discoRequestHashDict objectForKey:key]; + NSArray *jids = discoRequestHashDict[key]; NSUInteger i; for (i = 1; i < [jids count]; i++) { - XMPPJID *currentJid = [jids objectAtIndex:i]; + XMPPJID *currentJid = jids[i]; [discoRequestJidSet removeObject:currentJid]; @@ -1366,7 +1348,7 @@ - (void)maybeQueryNextJidWithHashKey:(NSString *)key dueToHashMismatch:(BOOL)has // Get the list of jids that have the same capabilities hash - NSMutableArray *jids = [discoRequestHashDict objectForKey:key]; + NSMutableArray *jids = discoRequestHashDict[key]; if (jids == nil) { XMPPLogWarn(@"%@: %@ - Key doesn't exist in discoRequestHashDict", THIS_FILE, THIS_METHOD); @@ -1376,8 +1358,8 @@ - (void)maybeQueryNextJidWithHashKey:(NSString *)key dueToHashMismatch:(BOOL)has // Get the index and jid of the fetch that just failed - NSUInteger requestIndex = [[jids objectAtIndex:0] unsignedIntegerValue]; - XMPPJID *jid = [jids objectAtIndex:requestIndex]; + NSUInteger requestIndex = [jids[0] unsignedIntegerValue]; + XMPPJID *jid = jids[requestIndex]; // Release the associated timer [self cancelTimeoutForDiscoRequestFromJID:jid]; @@ -1395,7 +1377,7 @@ - (void)maybeQueryNextJidWithHashKey:(NSString *)key dueToHashMismatch:(BOOL)has // Increment request index (and update object in jids array), requestIndex++; - [jids replaceObjectAtIndex:0 withObject:[NSNumber numberWithUnsignedInteger:requestIndex]]; + jids[0] = @(requestIndex); } // Do we have another jid that we can query? @@ -1403,7 +1385,7 @@ - (void)maybeQueryNextJidWithHashKey:(NSString *)key dueToHashMismatch:(BOOL)has if (requestIndex < [jids count]) { - jid = [jids objectAtIndex:requestIndex]; + jid = jids[requestIndex]; NSString *node = nil; NSString *ver = nil; @@ -1440,7 +1422,7 @@ - (void)maybeQueryNextJidWithHashKey:(NSString *)key dueToHashMismatch:(BOOL)has NSUInteger i; for (i = 1; i < [jids count]; i++) { - jid = [jids objectAtIndex:i]; + jid = jids[i]; [discoRequestJidSet removeObject:jid]; [xmppCapabilitiesStorage setCapabilitiesFetchFailedForJID:jid xmppStream:xmppStream]; @@ -1656,7 +1638,7 @@ - (void)setupTimeoutForDiscoRequestFromJID:(XMPPJID *)jid GCDTimerWrapper *timerWrapper = [[GCDTimerWrapper alloc] initWithDispatchTimer:timer]; - [discoTimerJidDict setObject:timerWrapper forKey:jid]; + discoTimerJidDict[jid] = timerWrapper; } - (void)setupTimeoutForDiscoRequestFromJID:(XMPPJID *)jid withHashKey:(NSString *)key @@ -1692,7 +1674,7 @@ - (void)setupTimeoutForDiscoRequestFromJID:(XMPPJID *)jid withHashKey:(NSString GCDTimerWrapper *timerWrapper = [[GCDTimerWrapper alloc] initWithDispatchTimer:timer]; - [discoTimerJidDict setObject:timerWrapper forKey:jid]; + discoTimerJidDict[jid] = timerWrapper; } - (void)cancelTimeoutForDiscoRequestFromJID:(XMPPJID *)jid @@ -1702,7 +1684,7 @@ - (void)cancelTimeoutForDiscoRequestFromJID:(XMPPJID *)jid XMPPLogTrace(); - GCDTimerWrapper *timerWrapper = [discoTimerJidDict objectForKey:jid]; + GCDTimerWrapper *timerWrapper = discoTimerJidDict[jid]; if (timerWrapper) { [timerWrapper cancel]; diff --git a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.h b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.h index 52f6e740aa..96f3d4a231 100644 --- a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.h +++ b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.h @@ -35,6 +35,12 @@ @property (strong) NSString *messageEntityName; @property (strong) NSString *contactEntityName; +/** + * Defines elements within an archived message that will be tested for content presence + * when determining whether to store the message. By default, only the body element is examined. + */ +@property (copy, nonatomic) NSArray *relevantContentXPaths; + - (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc; - (NSEntityDescription *)contactEntity:(NSManagedObjectContext *)moc; diff --git a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m index 6b0d2e9211..d4b801e475 100644 --- a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m +++ b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchivingCoreDataStorage.m @@ -20,6 +20,7 @@ @interface XMPPMessageArchivingCoreDataStorage () { NSString *messageEntityName; NSString *contactEntityName; + NSArray *relevantContentXPaths; } @end @@ -57,6 +58,8 @@ - (void)commonInit messageEntityName = @"XMPPMessageArchiving_Message_CoreDataObject"; contactEntityName = @"XMPPMessageArchiving_Contact_CoreDataObject"; + + relevantContentXPaths = @[@"./*[local-name()='body']"]; } /** @@ -166,14 +169,15 @@ - (XMPPMessageArchiving_Message_CoreDataObject *)composingMessageWithJid:(XMPPJI NSString *predicateFrmt = @"composing == YES AND bareJidStr == %@ AND outgoing == %@ AND streamBareJidStr == %@"; NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFrmt, - [messageJid bare], [NSNumber numberWithBool:isOutgoing], [streamJid bare]]; + [messageJid bare], @(isOutgoing), + [streamJid bare]]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:NO]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; fetchRequest.entity = messageEntity; fetchRequest.predicate = predicate; - fetchRequest.sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; + fetchRequest.sortDescriptors = @[sortDescriptor]; fetchRequest.fetchLimit = 1; NSError *error = nil; @@ -191,6 +195,26 @@ - (XMPPMessageArchiving_Message_CoreDataObject *)composingMessageWithJid:(XMPPJI return result; } +- (BOOL)messageContainsRelevantContent:(XMPPMessage *)message +{ + for (NSString *XPath in self.relevantContentXPaths) { + NSError *error; + NSArray *nodes = [message nodesForXPath:XPath error:&error]; + if (!nodes) { + XMPPLogError(@"%@: %@ - Error querying XPath (%@): %@", THIS_FILE, THIS_METHOD, XPath, error); + continue; + } + + for (NSXMLNode *node in nodes) { + if (node.stringValue.length > 0) { + return YES; + } + } + } + + return NO; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Public API //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -254,7 +278,7 @@ - (NSString *)messageEntityName __block NSString *result = nil; dispatch_block_t block = ^{ - result = messageEntityName; + result = self->messageEntityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -268,7 +292,7 @@ - (NSString *)messageEntityName - (void)setMessageEntityName:(NSString *)entityName { dispatch_block_t block = ^{ - messageEntityName = entityName; + self->messageEntityName = entityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -282,7 +306,7 @@ - (NSString *)contactEntityName __block NSString *result = nil; dispatch_block_t block = ^{ - result = contactEntityName; + result = self->contactEntityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -296,7 +320,7 @@ - (NSString *)contactEntityName - (void)setContactEntityName:(NSString *)entityName { dispatch_block_t block = ^{ - contactEntityName = entityName; + self->contactEntityName = entityName; }; if (dispatch_get_specific(storageQueueTag)) @@ -321,6 +345,36 @@ - (NSEntityDescription *)contactEntity:(NSManagedObjectContext *)moc return [NSEntityDescription entityForName:[self contactEntityName] inManagedObjectContext:moc]; } +- (NSArray *)relevantContentXPaths +{ + __block NSArray *result; + + dispatch_block_t block = ^{ + result = self->relevantContentXPaths; + }; + + if (dispatch_get_specific(storageQueueTag)) + block(); + else + dispatch_sync(storageQueue, block); + + return result; +} + +- (void)setRelevantContentXPaths:(NSArray *)relevantContentXPathsToSet +{ + NSArray *newValue = [relevantContentXPathsToSet copy]; + + dispatch_block_t block = ^{ + self->relevantContentXPaths = newValue; + }; + + if (dispatch_get_specific(storageQueueTag)) + block(); + else + dispatch_async(storageQueue, block); +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Storage Protocol //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -338,9 +392,9 @@ - (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStre BOOL isComposing = NO; BOOL shouldDeleteComposingMessage = NO; - if ([messageBody length] == 0) + if (![self messageContainsRelevantContent:message]) { - // Message doesn't have a body. + // Message doesn't have any content relevant for the module's user. // Check to see if it has a chat state (composing, paused, etc). isComposing = [message hasComposingChatState]; @@ -439,7 +493,7 @@ - (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStre // Create or update contact (if message with actual content) - if ([messageBody length] > 0) + if ([self messageContainsRelevantContent:message]) { BOOL didCreateNewContact = NO; @@ -460,7 +514,7 @@ - (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStre contact.mostRecentMessageTimestamp = archivedMessage.timestamp; contact.mostRecentMessageBody = archivedMessage.body; - contact.mostRecentMessageOutgoing = [NSNumber numberWithBool:isOutgoing]; + contact.mostRecentMessageOutgoing = @(isOutgoing); XMPPLogVerbose(@"New contact: %@", contact); diff --git a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchiving_Message_CoreDataObject.m b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchiving_Message_CoreDataObject.m index e19a567471..60de9c3e1e 100644 --- a/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchiving_Message_CoreDataObject.m +++ b/Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchiving_Message_CoreDataObject.m @@ -141,7 +141,7 @@ - (BOOL)isOutgoing - (void)setIsOutgoing:(BOOL)flag { - self.outgoing = [NSNumber numberWithBool:flag]; + self.outgoing = @(flag); } - (BOOL)isComposing @@ -151,7 +151,7 @@ - (BOOL)isComposing - (void)setIsComposing:(BOOL)flag { - self.composing = [NSNumber numberWithBool:flag]; + self.composing = @(flag); } #pragma mark Hooks diff --git a/Extensions/XEP-0136/XMPPMessageArchiving.m b/Extensions/XEP-0136/XMPPMessageArchiving.m index 57e8ce08e3..1032cbbfe4 100644 --- a/Extensions/XEP-0136/XMPPMessageArchiving.m +++ b/Extensions/XEP-0136/XMPPMessageArchiving.m @@ -109,7 +109,7 @@ - (BOOL)clientSideMessageArchivingOnly __block BOOL result = NO; dispatch_block_t block = ^{ - result = clientSideMessageArchivingOnly; + result = self->clientSideMessageArchivingOnly; }; if (dispatch_get_specific(moduleQueueTag)) @@ -123,7 +123,7 @@ - (BOOL)clientSideMessageArchivingOnly - (void)setClientSideMessageArchivingOnly:(BOOL)flag { dispatch_block_t block = ^{ - clientSideMessageArchivingOnly = flag; + self->clientSideMessageArchivingOnly = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -138,7 +138,7 @@ - (NSXMLElement *)preferences dispatch_block_t block = ^{ - result = [preferences copy]; + result = [self->preferences copy]; }; if (dispatch_get_specific(moduleQueueTag)) @@ -155,15 +155,15 @@ - (void)setPreferences:(NSXMLElement *)newPreferences // Update cached value - preferences = [newPreferences copy]; + self->preferences = [newPreferences copy]; // Update storage - if ([xmppMessageArchivingStorage respondsToSelector:@selector(setPreferences:forUser:)]) + if ([self->xmppMessageArchivingStorage respondsToSelector:@selector(setPreferences:forUser:)]) { - XMPPJID *myBareJid = [[xmppStream myJID] bareJID]; + XMPPJID *myBareJid = [[self->xmppStream myJID] bareJID]; - [xmppMessageArchivingStorage setPreferences:preferences forUser:myBareJid]; + [self->xmppMessageArchivingStorage setPreferences:self->preferences forUser:myBareJid]; } // Todo: @@ -352,7 +352,7 @@ - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender // NSXMLElement *pref = [NSXMLElement elementWithName:@"pref" xmlns:XMLNS_XMPP_ARCHIVE]; - XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:nil child:pref]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:[xmppStream generateUUID] child:pref]; [sender sendElement:iq]; } diff --git a/Extensions/XEP-0147/XMPPURI.h b/Extensions/XEP-0147/XMPPURI.h new file mode 100644 index 0000000000..0b9dbc8798 --- /dev/null +++ b/Extensions/XEP-0147/XMPPURI.h @@ -0,0 +1,71 @@ +// +// XMPPURI.h +// XMPPFramework +// +// Created by Christopher Ballinger on 5/15/15. +// Copyright (c) 2015 Chris Ballinger. All rights reserved. +// + +#import +#import "XMPPJID.h" + +/** + * For parsing and creating XMPP URIs RFC5122/XEP-0147 + * e.g. xmpp:username@domain.com?subscribe + * http://www.xmpp.org/extensions/xep-0147.html + */ +NS_ASSUME_NONNULL_BEGIN +@interface XMPPURI : NSObject + +/** + * User JID. e.g. romeo@montague.net + * Example: xmpp:romeo@montague.net + */ +@property (nonatomic, strong, readonly, nullable) XMPPJID *jid; + +/** + * Account JID. (Optional) + * Used to specify an account with which to perform an action. + * For example 'guest@example.com' would be the authority portion of + * xmpp://guest@example.com/support@example.com?message + * so the application would show a dialog with an outgoing message + * to support@example.com from the user's account guest@example.com. + */ +@property (nonatomic, strong, readonly, nullable) XMPPJID *accountJID; + +/** + * XMPP query action. e.g. subscribe + * For example, the query action below would be 'subscribe' + * xmpp:romeo@montague.net?subscribe + * For full list: http://xmpp.org/registrar/querytypes.html + */ +@property (nonatomic, strong, readonly, nullable) NSString *queryAction; + +/** + * XMPP query parameters. e.g. subject=Test + * + * For example the query parameters for + * xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message + * would be + * {"subject": "Test Message", + * "body": "Here's a test message"} + */ +@property (nonatomic, strong, readonly, nullable) NSDictionary *queryParameters; + +/** + * Generates URI string from jid, queryAction, and queryParameters + * e.g. xmpp:romeo@montague.net?subscribe + */ +- (nullable NSString*) uriString; + +// Parsing XMPP URIs +- (instancetype) initWithURL:(NSURL*)url; +- (instancetype) initWithURIString:(NSString*)uriString; + +// Creating XMPP URIs +- (instancetype) initWithJID:(XMPPJID*)jid + queryAction:(NSString*)queryAction + queryParameters:(nullable NSDictionary*)queryParameters; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0147/XMPPURI.m b/Extensions/XEP-0147/XMPPURI.m new file mode 100644 index 0000000000..ed2321fd6f --- /dev/null +++ b/Extensions/XEP-0147/XMPPURI.m @@ -0,0 +1,111 @@ +// +// XMPPURI.m +// XMPPFramework +// +// Created by Christopher Ballinger on 5/15/15. +// Copyright (c) 2015 Chris Ballinger. All rights reserved. +// + +#import "XMPPURI.h" + +@implementation XMPPURI + +- (instancetype) initWithURIString:(NSString *)uriString { + if (self = [super init]) { + [self parseURIString:uriString]; + } + return self; +} + +- (instancetype) initWithURL:(NSURL *)url { + if (self = [self initWithURIString:url.absoluteString]) { + } + return self; +} + +- (instancetype) initWithJID:(XMPPJID*)jid + queryAction:(NSString*)queryAction + queryParameters:(NSDictionary*)queryParameters { + if (self = [super init]) { + _jid = [jid copy]; + _queryAction = [queryAction copy]; + _queryParameters = [queryParameters copy]; + } + return self; +} + +- (NSString*) uriString { + if (!self.jid) { + return nil; + } + NSMutableString *uriString = [NSMutableString stringWithFormat:@"xmpp:%@", self.jid.bare]; + if (self.queryAction) { + [uriString appendFormat:@"?%@", self.queryAction]; + } + NSMutableCharacterSet *allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet.mutableCopy; + [allowedCharacterSet removeCharactersInString:@"'"]; // what other characters should be removed? + [self.queryParameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { + NSString *value = [obj stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; + [uriString appendFormat:@";%@=%@", key, value]; + }]; + return uriString; +} + +- (void) parseURIString:(NSString*)uriString { + NSString *authority = nil; + // Parse authority component + if ([uriString containsString:@"://"]) { + NSRange fullRange = NSMakeRange(0, uriString.length); + NSRange startRange = [uriString rangeOfString:@"://"]; + NSUInteger trailingLocation = startRange.location + startRange.length; + NSRange trailingRange = NSMakeRange(trailingLocation, uriString.length - trailingLocation); + NSRange endRange = [uriString rangeOfString:@"/" options:0 range:trailingRange]; + NSUInteger authorityLocation = startRange.location + startRange.length; + NSRange authorityRange = NSMakeRange(authorityLocation, endRange.location - authorityLocation); + authority = [uriString substringWithRange:authorityRange]; + NSString *stringToRemove = [NSString stringWithFormat:@"://%@/", authority]; + uriString = [uriString stringByReplacingOccurrencesOfString:stringToRemove withString:@":" options:0 range:fullRange]; + } + if (authority) { + _accountJID = [XMPPJID jidWithString:authority]; + } + + NSArray *uriComponents = [uriString componentsSeparatedByString:@":"]; + //NSString *scheme = nil; + NSString *jidString = nil; + + if (uriComponents.count >= 2) { + //scheme = uriComponents[0]; + NSString *path = uriComponents[1]; + if ([path containsString:@"?"]) { + NSArray *queryComponents = [path componentsSeparatedByString:@"?"]; + jidString = queryComponents[0]; + NSString *query = queryComponents[1]; + NSArray *queryKeys = [query componentsSeparatedByString:@";"]; + + NSMutableDictionary *queryParameters = [NSMutableDictionary dictionaryWithCapacity:queryKeys.count]; + [queryKeys enumerateObjectsUsingBlock:^(NSString *queryItem, NSUInteger idx, BOOL *stop) { + if (idx == 0) { + self->_queryAction = queryItem; + } else { + NSArray *keyValue = [queryItem componentsSeparatedByString:@"="]; + if (keyValue.count == 2) { + NSString *key = keyValue[0]; + NSString *value = [keyValue[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + if (key && value) { + queryParameters[key] = value; + } + } + } + }]; + _queryParameters = queryParameters; + } else { + jidString = path; + } + } + if (jidString) { + _jid = [XMPPJID jidWithString:jidString]; + } +} + +@end diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.h b/Extensions/XEP-0153/XMPPvCardAvatarModule.h index 77200b369f..174f49c354 100644 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.h +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.h @@ -12,8 +12,10 @@ #import -#if !TARGET_OS_IPHONE - #import +#if TARGET_OS_IPHONE + #import +#else + #import #endif #import "XMPP.h" @@ -23,18 +25,11 @@ @protocol XMPPvCardAvatarStorage; - +NS_ASSUME_NONNULL_BEGIN @interface XMPPvCardAvatarModule : XMPPModule -{ - __strong XMPPvCardTempModule *_xmppvCardTempModule; - __strong id _moduleStorage; - - BOOL _autoClearMyvcard; -} @property(nonatomic, strong, readonly) XMPPvCardTempModule *xmppvCardTempModule; - /* * XEP-0153 Section 4.2 rule 1 * @@ -46,11 +41,13 @@ @property(nonatomic, assign) BOOL autoClearMyvcard; -- (id)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule; -- (id)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; +- (instancetype)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule; +- (instancetype)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; -- (NSData *)photoDataForJID:(XMPPJID *)jid; +- (nullable NSData *)photoDataForJID:(XMPPJID *)jid; @end @@ -78,8 +75,8 @@ @protocol XMPPvCardAvatarStorage -- (NSData *)photoDataForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; -- (NSString *)photoHashForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; +- (nullable NSData *)photoDataForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; +- (nullable NSString *)photoHashForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; /** * Clears the vCardTemp from the store. @@ -88,4 +85,4 @@ - (void)clearvCardTempForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream; @end - +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0153/XMPPvCardAvatarModule.m b/Extensions/XEP-0153/XMPPvCardAvatarModule.m index e3d0447fcb..2da5b2b208 100755 --- a/Extensions/XEP-0153/XMPPvCardAvatarModule.m +++ b/Extensions/XEP-0153/XMPPvCardAvatarModule.m @@ -31,6 +31,13 @@ NSString *const kXMPPvCardAvatarNS = @"vcard-temp:x:update"; NSString *const kXMPPvCardAvatarPhotoElement = @"photo"; +@interface XMPPvCardAvatarModule() { + __strong XMPPvCardTempModule *_xmppvCardTempModule; + __strong id _moduleStorage; + + BOOL _autoClearMyvcard; +} +@end @implementation XMPPvCardAvatarModule @@ -38,22 +45,6 @@ @implementation XMPPvCardAvatarModule #pragma mark Init/dealloc //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (id)init -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPvCardAvatarModule.h are supported. - - return [self initWithvCardTempModule:nil dispatchQueue:NULL]; -} - -- (id)initWithDispatchQueue:(dispatch_queue_t)queue -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPvCardAvatarModule.h are supported. - - return [self initWithvCardTempModule:nil dispatchQueue:NULL]; -} - - (id)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule { return [self initWithvCardTempModule:xmppvCardTempModule dispatchQueue:NULL]; @@ -94,7 +85,7 @@ - (BOOL)autoClearMyvcard __block BOOL result = NO; dispatch_block_t block = ^{ - result = _autoClearMyvcard; + result = self->_autoClearMyvcard; }; if (dispatch_get_specific(moduleQueueTag)) @@ -108,7 +99,7 @@ - (BOOL)autoClearMyvcard - (void)setAutoClearMyvcard:(BOOL)flag { dispatch_block_t block = ^{ - _autoClearMyvcard = flag; + self->_autoClearMyvcard = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -132,11 +123,11 @@ - (NSData *)photoDataForJID:(XMPPJID *)jid dispatch_block_t block = ^{ @autoreleasepool { - photoData = [_moduleStorage photoDataForJID:jid xmppStream:xmppStream]; + photoData = [self->_moduleStorage photoDataForJID:jid xmppStream:self->xmppStream]; if (photoData == nil) { - [_xmppvCardTempModule vCardTempForJID:jid shouldFetch:YES]; + [self->_xmppvCardTempModule vCardTempForJID:jid shouldFetch:YES]; } }}; @@ -287,7 +278,7 @@ - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule - (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule{ //The vCard has been updated on the server so we need to cache it - [_xmppvCardTempModule fetchvCardTempForJID:[xmppStream myJID] ignoreStorage:YES]; + [_xmppvCardTempModule fetchvCardTempForJID:[xmppStream myJID] ignoreStorage:NO]; } - (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(NSXMLElement *)error{ diff --git a/Extensions/XEP-0172/XMPPMessage+XEP_0172.h b/Extensions/XEP-0172/XMPPMessage+XEP_0172.h index b6869136a1..bea9c27c36 100644 --- a/Extensions/XEP-0172/XMPPMessage+XEP_0172.h +++ b/Extensions/XEP-0172/XMPPMessage+XEP_0172.h @@ -1,8 +1,12 @@ #import #import "XMPPMessage.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (XEP_0172) -- (NSString *)nick; +@property (nonatomic, readonly, nullable) NSString *nick; + +- (void)addNick:(NSString *)nick; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0172/XMPPMessage+XEP_0172.m b/Extensions/XEP-0172/XMPPMessage+XEP_0172.m index 8cc97d5380..7ed445784b 100644 --- a/Extensions/XEP-0172/XMPPMessage+XEP_0172.m +++ b/Extensions/XEP-0172/XMPPMessage+XEP_0172.m @@ -10,4 +10,11 @@ - (NSString *)nick return [[self elementForName:@"nick" xmlns:XMLNS_NICK] stringValue]; } +- (void)addNick:(NSString *)nick +{ + NSXMLElement *nickElement = [NSXMLElement elementWithName:@"nick" xmlns:XMLNS_NICK]; + [nickElement addChild:[NSXMLNode textWithStringValue:nick]]; + [self addChild:nickElement]; +} + @end diff --git a/Extensions/XEP-0172/XMPPPresence+XEP_0172.h b/Extensions/XEP-0172/XMPPPresence+XEP_0172.h index 04edadce20..6dd81c26d1 100644 --- a/Extensions/XEP-0172/XMPPPresence+XEP_0172.h +++ b/Extensions/XEP-0172/XMPPPresence+XEP_0172.h @@ -1,8 +1,12 @@ #import #import "XMPPPresence.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPPresence (XEP_0172) -- (NSString *)nick; +@property (nonatomic, readonly, nullable) NSString *nick; + +- (void)addNick:(NSString *)nick; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0172/XMPPPresence+XEP_0172.m b/Extensions/XEP-0172/XMPPPresence+XEP_0172.m index 0cde58e51c..0eadef548a 100644 --- a/Extensions/XEP-0172/XMPPPresence+XEP_0172.m +++ b/Extensions/XEP-0172/XMPPPresence+XEP_0172.m @@ -9,4 +9,11 @@ - (NSString *)nick{ return [[self elementForName:@"nick" xmlns:XMLNS_NICK] stringValue]; } +- (void)addNick:(NSString *)nick +{ + NSXMLElement *nickElement = [NSXMLElement elementWithName:@"nick" xmlns:XMLNS_NICK]; + [nickElement addChild:[NSXMLNode textWithStringValue:nick]]; + [self addChild:nickElement]; +} + @end diff --git a/Extensions/XEP-0184/XMPPMessage+XEP_0184.h b/Extensions/XEP-0184/XMPPMessage+XEP_0184.h index 877e5c39ec..ab447fbbfc 100644 --- a/Extensions/XEP-0184/XMPPMessage+XEP_0184.h +++ b/Extensions/XEP-0184/XMPPMessage+XEP_0184.h @@ -1,14 +1,15 @@ #import #import "XMPPMessage.h" - +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (XEP_0184) -- (BOOL)hasReceiptRequest; -- (BOOL)hasReceiptResponse; -- (NSString *)receiptResponseID; -- (XMPPMessage *)generateReceiptResponse; +@property (nonatomic, readonly) BOOL hasReceiptRequest; +@property (nonatomic, readonly) BOOL hasReceiptResponse; +@property (nonatomic, readonly, nullable) NSString *receiptResponseID; +@property (nonatomic, readonly, nullable) XMPPMessage *generateReceiptResponse; - (void)addReceiptRequest; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0184/XMPPMessage+XEP_0184.m b/Extensions/XEP-0184/XMPPMessage+XEP_0184.m index aa4b061f70..a23f2e5833 100644 --- a/Extensions/XEP-0184/XMPPMessage+XEP_0184.m +++ b/Extensions/XEP-0184/XMPPMessage+XEP_0184.m @@ -1,5 +1,6 @@ #import "XMPPMessage+XEP_0184.h" #import "NSXMLElement+XMPP.h" +#import "XMPPMessage+XEP0045.h" @implementation XMPPMessage (XEP_0184) @@ -37,7 +38,19 @@ - (XMPPMessage *)generateReceiptResponse NSXMLElement *message = [NSXMLElement elementWithName:@"message"]; - NSString *to = [self fromStr]; + NSString *type = [self type]; + + if (type) { + [message addAttributeWithName:@"type" stringValue:type]; + } + + NSString *to = [self fromStr]; + + if([self isGroupChatMessage]) { + to = [[self from] bare]; + [message addAttributeWithName:@"type" stringValue:@"groupchat"]; + } + if (to) { [message addAttributeWithName:@"to" stringValue:to]; diff --git a/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.h b/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.h index 16070fe809..2dd149fa1e 100644 --- a/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.h +++ b/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.h @@ -2,10 +2,13 @@ #define _XMPP_MESSAGE_DELIVERY_RECEIPTS_H +@class XMPPMessage; + /** * XMPPMessageDeliveryReceipts can be configured to automatically send delivery receipts and requests in accordance to XEP-0184 **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessageDeliveryReceipts : XMPPModule /** @@ -29,4 +32,19 @@ @property (assign) BOOL autoSendMessageDeliveryReceipts; -@end \ No newline at end of file +@end + +/** + * A protocol defining @c XMPPManagedMessaging module delegate API. +**/ +@protocol XMPPMessageDeliveryReceiptsDelegate + +@optional + +/** + * Notifies the delegate of a receipt response message received in the stream. +**/ +- (void)xmppMessageDeliveryReceipts:(XMPPMessageDeliveryReceipts *)xmppMessageDeliveryReceipts didReceiveReceiptResponseMessage:(XMPPMessage *)message; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.m b/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.m index 564bdd9773..6f469da0e3 100644 --- a/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.m +++ b/Extensions/XEP-0184/XMPPMessageDeliveryReceipts.m @@ -64,7 +64,7 @@ - (BOOL)autoSendMessageDeliveryRequests __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoSendMessageDeliveryRequests; + result = self->autoSendMessageDeliveryRequests; }; if (dispatch_get_specific(moduleQueueTag)) @@ -78,7 +78,7 @@ - (BOOL)autoSendMessageDeliveryRequests - (void)setAutoSendMessageDeliveryRequests:(BOOL)flag { dispatch_block_t block = ^{ - autoSendMessageDeliveryRequests = flag; + self->autoSendMessageDeliveryRequests = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -92,7 +92,7 @@ - (BOOL)autoSendMessageDeliveryReceipts __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoSendMessageDeliveryReceipts; + result = self->autoSendMessageDeliveryReceipts; }; if (dispatch_get_specific(moduleQueueTag)) @@ -106,7 +106,7 @@ - (BOOL)autoSendMessageDeliveryReceipts - (void)setAutoSendMessageDeliveryReceipts:(BOOL)flag { dispatch_block_t block = ^{ - autoSendMessageDeliveryReceipts = flag; + self->autoSendMessageDeliveryReceipts = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -129,6 +129,11 @@ - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message [sender sendElement:generatedReceiptResponse]; } } + + if ([message hasReceiptResponse]) + { + [multicastDelegate xmppMessageDeliveryReceipts:self didReceiveReceiptResponseMessage:message]; + } } - (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message diff --git a/Extensions/XEP-0191/XMPPBlocking.h b/Extensions/XEP-0191/XMPPBlocking.h index 36734fee5a..2aebb65e75 100644 --- a/Extensions/XEP-0191/XMPPBlocking.h +++ b/Extensions/XEP-0191/XMPPBlocking.h @@ -1,9 +1,7 @@ #import #import "XMPPModule.h" -#if TARGET_OS_IPHONE -#import "DDXML.h" -#endif +@import KissXML; #define _XMPP_BLOCKING_H diff --git a/Extensions/XEP-0191/XMPPBlocking.m b/Extensions/XEP-0191/XMPPBlocking.m index 520dfd5293..fa58c5f0ef 100644 --- a/Extensions/XEP-0191/XMPPBlocking.m +++ b/Extensions/XEP-0191/XMPPBlocking.m @@ -122,7 +122,7 @@ - (BOOL)autoRetrieveBlockingListItems __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = autoRetrieveBlockingListItems; + result = self->autoRetrieveBlockingListItems; }); return result; @@ -133,7 +133,7 @@ - (void)setAutoRetrieveBlockingListItems:(BOOL)flag { dispatch_block_t block = ^{ - autoRetrieveBlockingListItems = flag; + self->autoRetrieveBlockingListItems = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -153,7 +153,7 @@ - (BOOL)autoClearBlockingListInfo __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = autoClearBlockingListInfo; + result = self->autoClearBlockingListInfo; }); return result; @@ -164,7 +164,7 @@ - (void)setAutoClearBlockingListInfo:(BOOL)flag { dispatch_block_t block = ^{ - autoClearBlockingListInfo = flag; + self->autoClearBlockingListInfo = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -208,7 +208,7 @@ - (void)clearBlockingListInfo { dispatch_async(moduleQueue, ^{ @autoreleasepool { - [blockingDict removeAllObjects]; + [self->blockingDict removeAllObjects]; }}); } } @@ -225,7 +225,7 @@ - (NSArray*)blockingList dispatch_sync(moduleQueue, ^{ @autoreleasepool { - result = [[blockingDict allKeys] copy]; + result = [[self->blockingDict allKeys] copy]; }}); return result; @@ -236,10 +236,10 @@ - (void)blockJID:(XMPPJID*)xmppJID { XMPPLogTrace(); - id value = [blockingDict objectForKey:[xmppJID full]]; + id value = blockingDict[[xmppJID full]]; if (value == nil) { - [blockingDict setObject:[NSNull null] forKey:[xmppJID full]]; + blockingDict[[xmppJID full]] = [NSNull null]; } // @@ -268,7 +268,7 @@ - (void)unblockJID:(XMPPJID*)xmppJID { XMPPLogTrace(); - id value = [blockingDict objectForKey:[xmppJID full]]; + id value = blockingDict[[xmppJID full]]; if (value != nil) { [blockingDict removeObjectForKey:[xmppJID full]]; @@ -297,7 +297,7 @@ - (void)unblockJID:(XMPPJID*)xmppJID - (BOOL)containsJID:(XMPPJID*)xmppJID { - if ([blockingDict objectForKey:[xmppJID full]]) + if (blockingDict[[xmppJID full]]) { return true; } @@ -349,7 +349,7 @@ - (void)addQueryInfo:(XMPPBlockingQueryInfo *)queryInfo withKey:(NSString *)uuid queryInfo.timer = timer; // Add to dictionary - [pendingQueries setObject:queryInfo forKey:uuid]; + pendingQueries[uuid] = queryInfo; } - (void)removeQueryInfo:(XMPPBlockingQueryInfo *)queryInfo withKey:(NSString *)uuid @@ -385,7 +385,7 @@ - (void)processQuery:(XMPPBlockingQueryInfo *)queryInfo withFailureCode:(XMPPBlo - (void)queryTimeout:(NSString *)uuid { - XMPPBlockingQueryInfo *queryInfo = [pendingQueries objectForKey:uuid]; + XMPPBlockingQueryInfo *queryInfo = pendingQueries[uuid]; if (queryInfo) { [self processQuery:queryInfo withFailureCode:XMPPBlockingQueryTimeout]; @@ -417,10 +417,10 @@ - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPBlockingQueryInfo *)quer NSString *name = [listItem attributeStringValueForName:@"jid"]; if (name) { - id value = [blockingDict objectForKey:name]; + id value = blockingDict[name]; if (value == nil) { - [blockingDict setObject:[NSNull null] forKey:name]; + blockingDict[name] = [NSNull null]; } } } @@ -459,12 +459,12 @@ - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPBlockingQueryInfo *)quer } else { - XMPPBlockingQueryInfo *queryInfo = [pendingQueries objectForKey:[iq elementID]]; + XMPPBlockingQueryInfo *queryInfo = pendingQueries[[iq elementID]]; - id value = [blockingDict objectForKey:[queryInfo.blockingXMPPJID full]]; + id value = blockingDict[[queryInfo.blockingXMPPJID full]]; if (value == nil) { - [blockingDict setObject:[NSNull null] forKey:[queryInfo.blockingXMPPJID full]]; + blockingDict[[queryInfo.blockingXMPPJID full]] = [NSNull null]; } [multicastDelegate xmppBlocking:self didNotBlockJID:queryInfo.blockingXMPPJID error:iq]; @@ -481,12 +481,12 @@ - (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPBlockingQueryInfo *)quer } else { - XMPPBlockingQueryInfo *queryInfo = [pendingQueries objectForKey:[iq elementID]]; + XMPPBlockingQueryInfo *queryInfo = pendingQueries[[iq elementID]]; - id value = [blockingDict objectForKey:[queryInfo.blockingXMPPJID full]]; + id value = blockingDict[[queryInfo.blockingXMPPJID full]]; if (value == nil) { - [blockingDict setObject:[NSNull null] forKey:queryInfo.blockingXMPPJID]; + blockingDict[queryInfo.blockingXMPPJID] = [NSNull null]; } [multicastDelegate xmppBlocking:self didNotUnblockAllDueToError:iq]; @@ -547,7 +547,7 @@ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { // This may be a response to a query we sent - XMPPBlockingQueryInfo *queryInfo = [pendingQueries objectForKey:[iq elementID]]; + XMPPBlockingQueryInfo *queryInfo = pendingQueries[[iq elementID]]; if (queryInfo) @@ -568,7 +568,7 @@ -(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error for (NSString *uuid in pendingQueries) { - XMPPBlockingQueryInfo *queryInfo = [pendingQueries objectForKey:uuid]; + XMPPBlockingQueryInfo *queryInfo = pendingQueries[uuid]; [self processQuery:queryInfo withFailureCode:XMPPBlockingDisconnect]; } diff --git a/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.h b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.h new file mode 100644 index 0000000000..ff64d648e9 --- /dev/null +++ b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.h @@ -0,0 +1,34 @@ +#import "XMPPModule.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessage; + +/** + A module working in tandem with @c XMPPStreamManagement to trace outgoing message stream acknowledgements. + + This module only monitors messages with @c elementID assigned. The rationale behind this is that any potential retransmissions + of messages without IDs will cause deduplication issues on the receiving end. + */ +@interface XMPPManagedMessaging : XMPPModule + +@end + +/// A protocol defining @c XMPPManagedMessaging module delegate API. +@protocol XMPPManagedMessagingDelegate + +@optional + +/// Notifies the delegate that a message subject to monitoring has been sent in the stream. +- (void)xmppManagedMessaging:(XMPPManagedMessaging *)sender didBeginMonitoringOutgoingMessage:(XMPPMessage *)message; + +/// Notifies the delegate that @c XMPPStreamManagement module has received server acknowledgement for sent messages with given IDs. +- (void)xmppManagedMessaging:(XMPPManagedMessaging *)sender didConfirmSentMessagesWithIDs:(NSArray *)messageIDs; + +/// @brief Notifies the delegate that post-reauthentication message acknowledgement processing is finished. +/// At this point, no more acknowledgements for currently monitored messages are to be expected. +- (void)xmppManagedMessagingDidFinishProcessingPreviousStreamConfirmations:(XMPPManagedMessaging *)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.m b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.m new file mode 100644 index 0000000000..ba0cf9e302 --- /dev/null +++ b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.m @@ -0,0 +1,112 @@ +#import "XMPPManagedMessaging.h" +#import "XMPPStreamManagement.h" +#import "XMPPLogging.h" + +// Log levels: off, error, warn, info, verbose +// Log flags: trace +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +static NSString * const XMPPManagedMessagingURLScheme = @"xmppmanagedmessage"; + +@implementation XMPPManagedMessaging + +- (void)didActivate +{ + XMPPLogTrace(); + [self.xmppStream autoAddDelegate:self delegateQueue:self.moduleQueue toModulesOfClass:[XMPPStreamManagement class]]; +} + +- (void)willDeactivate +{ + XMPPLogTrace(); + [self.xmppStream removeAutoDelegate:self delegateQueue:self.moduleQueue fromModulesOfClass:[XMPPStreamManagement class]]; +} + +- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message +{ + XMPPLogTrace(); + + if (![message elementID]) { + XMPPLogWarn(@"Sent message without an ID excluded from managed messaging"); + return; + } + + XMPPLogInfo(@"Registering message with ID=%@ for managed messaging", [message elementID]); + [multicastDelegate xmppManagedMessaging:self didBeginMonitoringOutgoingMessage:message]; +} + +- (id)xmppStreamManagement:(XMPPStreamManagement *)sender stanzaIdForSentElement:(XMPPElement *)element +{ + if (![element isKindOfClass:[XMPPMessage class]] || ![element elementID]) { + return nil; + } + + NSURLComponents *managedMessageURLComponents = [[NSURLComponents alloc] init]; + managedMessageURLComponents.scheme = XMPPManagedMessagingURLScheme; + managedMessageURLComponents.path = [element elementID]; + + return managedMessageURLComponents.URL; +} + +- (void)xmppStreamManagement:(XMPPStreamManagement *)sender didReceiveAckForStanzaIds:(NSArray *)stanzaIds +{ + XMPPLogTrace(); + + NSArray *resumeStanzaIDs; + [sender didResumeWithAckedStanzaIds:&resumeStanzaIDs serverResponse:nil]; + if ([resumeStanzaIDs isEqualToArray:stanzaIds]) { + // Handled in -xmppStreamDidAuthenticate: + return; + } + + [self processStreamManagementAcknowledgementForStanzaIDs:stanzaIds]; +} + +- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender +{ + XMPPLogTrace(); + + dispatch_group_t stanzaAcknowledgementGroup = dispatch_group_create(); + + [sender enumerateModulesOfClass:[XMPPStreamManagement class] withBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) { + NSArray *acknowledgedStanzaIDs; + [(XMPPStreamManagement *)module didResumeWithAckedStanzaIds:&acknowledgedStanzaIDs serverResponse:nil]; + if (acknowledgedStanzaIDs.count == 0) { + return; + } + + dispatch_group_async(stanzaAcknowledgementGroup, self.moduleQueue, ^{ + [self processStreamManagementAcknowledgementForStanzaIDs:acknowledgedStanzaIDs]; + }); + }]; + + dispatch_group_notify(stanzaAcknowledgementGroup, self.moduleQueue, ^{ + [self->multicastDelegate xmppManagedMessagingDidFinishProcessingPreviousStreamConfirmations:self]; + }); +} + +- (void)processStreamManagementAcknowledgementForStanzaIDs:(NSArray *)stanzaIDs +{ + NSMutableArray *managedMessageIDs = [NSMutableArray array]; + for (id stanzaID in stanzaIDs) { + if (![stanzaID isKindOfClass:[NSURL class]] || ![((NSURL *)stanzaID).scheme isEqualToString:XMPPManagedMessagingURLScheme]) { + continue; + } + // Extracting path directly from NSURL does not work if it doesn't start with "/" + NSURLComponents *managedMessageURLComponents = [[NSURLComponents alloc] initWithURL:stanzaID resolvingAgainstBaseURL:NO]; + [managedMessageIDs addObject:managedMessageURLComponents.path]; + } + + if (managedMessageIDs.count == 0) { + return; + } + + XMPPLogInfo(@"Confirming managed messages with IDs={%@}", [managedMessageIDs componentsJoinedByString:@","]); + [multicastDelegate xmppManagedMessaging:self didConfirmSentMessagesWithIDs:managedMessageIDs]; +} + +@end diff --git a/Extensions/XEP-0198/Memory Storage/XMPPStreamManagementMemoryStorage.m b/Extensions/XEP-0198/Memory Storage/XMPPStreamManagementMemoryStorage.m index 8a4397a73c..34ff1fa1d6 100644 --- a/Extensions/XEP-0198/Memory Storage/XMPPStreamManagementMemoryStorage.m +++ b/Extensions/XEP-0198/Memory Storage/XMPPStreamManagementMemoryStorage.m @@ -182,10 +182,10 @@ - (void)getResumptionId:(NSString **)resumptionIdPtr * Invoked when the extension needs values from a previous session. * This method is used to get values needed in order to resume a previous stream. **/ -- (void)getLastHandledByClient:(uint32_t *)lastHandledByClientPtr - lastHandledByServer:(uint32_t *)lastHandledByServerPtr - pendingOutgoingStanzas:(NSArray **)pendingOutgoingStanzasPtr - forStream:(XMPPStream *)stream; +- (void)getLastHandledByClient:(uint32_t * _Nullable)lastHandledByClientPtr + lastHandledByServer:(uint32_t * _Nullable)lastHandledByServerPtr + pendingOutgoingStanzas:(NSArray * _Nullable * _Nullable)pendingOutgoingStanzasPtr + forStream:(XMPPStream *)stream { if (lastHandledByClientPtr) *lastHandledByClientPtr = lastHandledByClient; if (lastHandledByServerPtr) *lastHandledByServerPtr = lastHandledByServer; diff --git a/Extensions/XEP-0198/Private/XMPPStreamManagementStanzas.h b/Extensions/XEP-0198/Private/XMPPStreamManagementStanzas.h index 78732ef181..e2b7299000 100644 --- a/Extensions/XEP-0198/Private/XMPPStreamManagementStanzas.h +++ b/Extensions/XEP-0198/Private/XMPPStreamManagementStanzas.h @@ -7,12 +7,13 @@ * The translation from element to stanzaId may be an asynchronous process, * so this structure is used to assist in the process. **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPStreamManagementOutgoingStanza : NSObject - (instancetype)initAwaitingStanzaId; - (instancetype)initWithStanzaId:(id)stanzaId; -@property (nonatomic, strong, readwrite) id stanzaId; +@property (nonatomic, strong, readwrite, nullable) id stanzaId; @property (nonatomic, assign, readwrite) BOOL awaitingStanzaId; @end @@ -27,9 +28,10 @@ **/ @interface XMPPStreamManagementIncomingStanza : NSObject -- (instancetype)initWithStanzaId:(id)stanzaId isHandled:(BOOL)isHandled; +- (instancetype)initWithStanzaId:(nullable id)stanzaId isHandled:(BOOL)isHandled; -@property (nonatomic, strong, readwrite) id stanzaId; +@property (nonatomic, strong, readwrite, nullable) id stanzaId; @property (nonatomic, assign, readwrite) BOOL isHandled; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0198/XMPPStreamManagement.h b/Extensions/XEP-0198/XMPPStreamManagement.h index f49fec4507..506c9de9ab 100644 --- a/Extensions/XEP-0198/XMPPStreamManagement.h +++ b/Extensions/XEP-0198/XMPPStreamManagement.h @@ -4,8 +4,9 @@ #define _XMPP_STREAM_MANAGEMENT_H @protocol XMPPStreamManagementStorage; +@class XMPPStreamManagementOutgoingStanza; - +NS_ASSUME_NONNULL_BEGIN @interface XMPPStreamManagement : XMPPModule /** @@ -20,8 +21,10 @@ * @param queue * The standard dispatch_queue option, with which to run the extension on. **/ -- (id)initWithStorage:(id )storage; -- (id)initWithStorage:(id )storage dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDispatchQueue:(nullable dispatch_queue_t)queue NS_UNAVAILABLE; +- (instancetype)initWithStorage:(id )storage; +- (instancetype)initWithStorage:(id )storage dispatchQueue:(nullable dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; @property (nonatomic, strong, readonly) id storage; @@ -109,8 +112,8 @@ * YES if the stream was resumed. * NO otherwise. **/ -- (BOOL)didResumeWithAckedStanzaIds:(NSArray **)stanzaIdsPtr - serverResponse:(NSXMLElement **)responsePtr; +- (BOOL)didResumeWithAckedStanzaIds:(NSArray * _Nullable * _Nullable)stanzaIdsPtr + serverResponse:(NSXMLElement * _Nullable * _Nullable)responsePtr; /** * Returns YES if the stream can be resumed. @@ -170,7 +173,7 @@ * * @see automaticallyRequestAcksAfterStanzaCount:orTimeout: **/ -- (void)getAutomaticallyRequestAcksAfterStanzaCount:(NSUInteger *)stanzaCountPtr orTimeout:(NSTimeInterval *)timeoutPtr; +- (void)getAutomaticallyRequestAcksAfterStanzaCount:(NSUInteger * _Nullable)stanzaCountPtr orTimeout:(NSTimeInterval * _Nullable)timeoutPtr; #pragma mark Sending Acks @@ -220,7 +223,7 @@ * * @see automaticallySendAcksAfterStanzaCount:orTimeout: **/ -- (void)getAutomaticallySendAcksAfterStanzaCount:(NSUInteger *)stanzaCountPtr orTimeout:(NSTimeInterval *)timeoutPtr; +- (void)getAutomaticallySendAcksAfterStanzaCount:(NSUInteger * _Nullable)stanzaCountPtr orTimeout:(NSTimeInterval * _Nullable)timeoutPtr; /** * If an explicit request is received from the server, should we delay sending the ack ? @@ -318,7 +321,7 @@ * * For more information, see the delegate method xmppStreamManagement:stanzaIdForSentElement: **/ -- (void)xmppStreamManagement:(XMPPStreamManagement *)sender didReceiveAckForStanzaIds:(NSArray *)stanzaIds; +- (void)xmppStreamManagement:(XMPPStreamManagement *)sender didReceiveAckForStanzaIds:(NSArray *)stanzaIds; /** * XEP-0198 reports the following regarding duplicate stanzas: @@ -352,7 +355,7 @@ * If the stanza isn't assigned a stanzaId (via a delegate method), * and it doesn't have an elementId, then it isn't reported in the acked stanzaIds array. **/ -- (id)xmppStreamManagement:(XMPPStreamManagement *)sender stanzaIdForSentElement:(XMPPElement *)element; +- (nullable id)xmppStreamManagement:(XMPPStreamManagement *)sender stanzaIdForSentElement:(XMPPElement *)element; /** * It's critically important to understand what an ACK means. @@ -397,8 +400,8 @@ * @see markHandledStanzaId: **/ - (void)xmppStreamManagement:(XMPPStreamManagement *)sender - getIsHandled:(BOOL *)isHandledPtr - stanzaId:(id *)stanzaIdPtr + getIsHandled:(BOOL * _Nullable)isHandledPtr + stanzaId:(id _Nullable * _Nullable)stanzaIdPtr forReceivedElement:(XMPPElement *)element; @end @@ -462,9 +465,9 @@ * - lastHandledByServer * - pendingOutgoingStanzas **/ -- (void)setResumptionId:(NSString *)resumptionId +- (void)setResumptionId:(nullable NSString *)resumptionId timeout:(uint32_t)timeout - lastDisconnect:(NSDate *)date + lastDisconnect:(nullable NSDate *)date forStream:(XMPPStream *)stream; /** @@ -482,7 +485,7 @@ * @param stream * The associated xmppStream (standard parameter for storage classes) **/ -- (void)setLastDisconnect:(NSDate *)date +- (void)setLastDisconnect:(nullable NSDate *)date lastHandledByClient:(uint32_t)lastHandledByClient forStream:(XMPPStream *)stream; @@ -507,9 +510,9 @@ * @param stream * The associated xmppStream (standard parameter for storage classes) **/ -- (void)setLastDisconnect:(NSDate *)date +- (void)setLastDisconnect:(nullable NSDate *)date lastHandledByServer:(uint32_t)lastHandledByServer - pendingOutgoingStanzas:(NSArray *)pendingOutgoingStanzas + pendingOutgoingStanzas:(nullable NSArray *)pendingOutgoingStanzas forStream:(XMPPStream *)stream; @@ -570,28 +573,28 @@ * @param stream * The associated xmppStream (standard parameter for storage classes) **/ -- (void)setLastDisconnect:(NSDate *)date +- (void)setLastDisconnect:(nullable NSDate *)date lastHandledByClient:(uint32_t)lastHandledByClient lastHandledByServer:(uint32_t)lastHandledByServer - pendingOutgoingStanzas:(NSArray *)pendingOutgoingStanzas + pendingOutgoingStanzas:(nullable NSArray *)pendingOutgoingStanzas forStream:(XMPPStream *)stream; /** * Invoked when the extension needs values from a previous session. * This method is used to get values needed in order to determine if it can resume a previous stream. **/ -- (void)getResumptionId:(NSString **)resumptionIdPtr - timeout:(uint32_t *)timeoutPtr - lastDisconnect:(NSDate **)lastDisconnectPtr +- (void)getResumptionId:(NSString * _Nullable * _Nullable)resumptionIdPtr + timeout:(uint32_t * _Nullable)timeoutPtr + lastDisconnect:(NSDate * _Nullable * _Nullable)lastDisconnectPtr forStream:(XMPPStream *)stream; /** * Invoked when the extension needs values from a previous session. * This method is used to get values needed in order to resume a previous stream. **/ -- (void)getLastHandledByClient:(uint32_t *)lastHandledByClientPtr - lastHandledByServer:(uint32_t *)lastHandledByServerPtr - pendingOutgoingStanzas:(NSArray **)pendingOutgoingStanzasPtr +- (void)getLastHandledByClient:(uint32_t * _Nullable)lastHandledByClientPtr + lastHandledByServer:(uint32_t * _Nullable)lastHandledByServerPtr + pendingOutgoingStanzas:(NSArray * _Nullable * _Nullable)pendingOutgoingStanzasPtr forStream:(XMPPStream *)stream; /** @@ -612,6 +615,7 @@ /** * Returns whether or not the server's includes . **/ -- (BOOL)supportsStreamManagement; +@property (nonatomic, readonly) BOOL supportsStreamManagement; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0198/XMPPStreamManagement.m b/Extensions/XEP-0198/XMPPStreamManagement.m index 4197df04a0..bfbd520966 100644 --- a/Extensions/XEP-0198/XMPPStreamManagement.m +++ b/Extensions/XEP-0198/XMPPStreamManagement.m @@ -97,22 +97,6 @@ @implementation XMPPStreamManagement @synthesize storage = storage; -- (id)init -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPStreamManagement.h are supported. - - return [self initWithStorage:nil dispatchQueue:NULL]; -} - -- (id)initWithDispatchQueue:(dispatch_queue_t)queue -{ - // This will cause a crash - it's designed to. - // Only the init methods listed in XMPPStreamManagement.h are supported. - - return [self initWithStorage:nil dispatchQueue:queue]; -} - - (id)initWithStorage:(id )inStorage { return [self initWithStorage:inStorage dispatchQueue:NULL]; @@ -161,7 +145,7 @@ - (BOOL)autoResume __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoResume; + result = self->autoResume; }; if (dispatch_get_specific(moduleQueueTag)) @@ -177,7 +161,7 @@ - (void)setAutoResume:(BOOL)newAutoResume XMPPLogTrace(); dispatch_block_t block = ^{ - autoResume = newAutoResume; + self->autoResume = newAutoResume; }; if (dispatch_get_specific(moduleQueueTag)) @@ -192,13 +176,13 @@ - (void)automaticallyRequestAcksAfterStanzaCount:(NSUInteger)stanzaCount orTimeo dispatch_block_t block = ^{ @autoreleasepool{ - autoRequest_stanzaCount = stanzaCount; - autoRequest_timeout = MAX(0.0, timeout); + self->autoRequest_stanzaCount = stanzaCount; + self->autoRequest_timeout = MAX(0.0, timeout); - if (autoRequestTimer) { - [autoRequestTimer updateTimeout:autoRequest_timeout fromOriginalStartTime:YES]; + if (self->autoRequestTimer) { + [self->autoRequestTimer updateTimeout:self->autoRequest_timeout fromOriginalStartTime:YES]; } - if (isStarted) { + if (self->isStarted) { [self maybeRequestAck]; } }}; @@ -218,8 +202,8 @@ - (void)getAutomaticallyRequestAcksAfterStanzaCount:(NSUInteger *)stanzaCountPtr dispatch_block_t block = ^{ - stanzaCount = autoRequest_stanzaCount; - timeout = autoRequest_timeout; + stanzaCount = self->autoRequest_stanzaCount; + timeout = self->autoRequest_timeout; }; if (dispatch_get_specific(moduleQueueTag)) @@ -237,13 +221,13 @@ - (void)automaticallySendAcksAfterStanzaCount:(NSUInteger)stanzaCount orTimeout: dispatch_block_t block = ^{ @autoreleasepool{ - autoAck_stanzaCount = stanzaCount; - autoAck_timeout = MAX(0.0, timeout); + self->autoAck_stanzaCount = stanzaCount; + self->autoAck_timeout = MAX(0.0, timeout); - if (autoAckTimer) { - [autoAckTimer updateTimeout:autoAck_timeout fromOriginalStartTime:YES]; + if (self->autoAckTimer) { + [self->autoAckTimer updateTimeout:self->autoAck_timeout fromOriginalStartTime:YES]; } - if (isStarted) { + if (self->isStarted) { [self maybeSendAck]; } }}; @@ -263,8 +247,8 @@ - (void)getAutomaticallySendAcksAfterStanzaCount:(NSUInteger *)stanzaCountPtr or dispatch_block_t block = ^{ - stanzaCount = autoAck_stanzaCount; - timeout = autoAck_timeout; + stanzaCount = self->autoAck_stanzaCount; + timeout = self->autoAck_timeout; }; if (dispatch_get_specific(moduleQueueTag)) @@ -284,7 +268,7 @@ - (NSTimeInterval)ackResponseDelay dispatch_block_t block = ^{ - delay = ackResponseDelay; + delay = self->ackResponseDelay; }; if (dispatch_get_specific(moduleQueueTag)) @@ -301,7 +285,7 @@ - (void)setAckResponseDelay:(NSTimeInterval)delay dispatch_block_t block = ^{ - ackResponseDelay = delay; + self->ackResponseDelay = delay; }; if (dispatch_get_specific(moduleQueueTag)) @@ -341,12 +325,12 @@ - (void)enableStreamManagementWithResumption:(BOOL)supportsResumption maxTimeout { dispatch_block_t block = ^{ @autoreleasepool{ - if (isStarted) + if (self->isStarted) { XMPPLogWarn(@"Stream management is already enabled/resumed."); return; } - if (enableQueued || enableSent) + if (self->enableQueued || self->enableSent) { XMPPLogWarn(@"Stream management is already started (pending response from server)."); return; @@ -354,16 +338,16 @@ - (void)enableStreamManagementWithResumption:(BOOL)supportsResumption maxTimeout // State transition cleanup - [unackedByServer removeAllObjects]; - unackedByServer_lastRequestOffset = 0; + [self->unackedByServer removeAllObjects]; + self->unackedByServer_lastRequestOffset = 0; - [unackedByClient removeAllObjects]; - unackedByClient_lastAckOffset = 0; + [self->unackedByClient removeAllObjects]; + self->unackedByClient_lastAckOffset = 0; - unprocessedReceivedAcks = nil; + self->unprocessedReceivedAcks = nil; - pendingHandledStanzaIds = nil; - outstandingStanzaIds = 0; + self->pendingHandledStanzaIds = nil; + self->outstandingStanzaIds = 0; // Send enable stanza: // @@ -378,10 +362,10 @@ - (void)enableStreamManagementWithResumption:(BOOL)supportsResumption maxTimeout [enable addAttributeWithName:@"max" stringValue:[NSString stringWithFormat:@"%.0f", maxTimeout]]; } - [xmppStream sendElement:enable]; + [self->xmppStream sendElement:enable]; - enableQueued = YES; - requestedMax = (maxTimeout > 0.0) ? (uint32_t)maxTimeout : (uint32_t)0; + self->enableQueued = YES; + self->requestedMax = (maxTimeout > 0.0) ? (uint32_t)maxTimeout : (uint32_t)0; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -417,7 +401,7 @@ - (BOOL)canResumeStreamWithResumptionId:(NSString *)resumptionId XMPPLogVerbose(@"%@: Cannot resume stream: invalid lastDisconnect - appears to be in future", THIS_FILE); return NO; } - if ((uint32_t)elapsed > timeout) // too much time has elapsed + if (timeout > 0 && (uint32_t)elapsed > timeout) // too much time has elapsed { XMPPLogVerbose(@"%@: Cannot resume stream: elapsed(%u) > timeout(%u)", THIS_FILE, (uint32_t)elapsed, timeout); return NO; @@ -442,7 +426,7 @@ - (BOOL)canResumeStream dispatch_block_t block = ^{ @autoreleasepool{ - if (isStarted || enableQueued || enableSent) { + if (self->isStarted || self->enableQueued || self->enableSent) { return_from_block; } @@ -450,10 +434,10 @@ - (BOOL)canResumeStream uint32_t timeout = 0; NSDate *lastDisconnect = nil; - [storage getResumptionId:&resumptionId - timeout:&timeout - lastDisconnect:&lastDisconnect - forStream:xmppStream]; + [self->storage getResumptionId:&resumptionId + timeout:&timeout + lastDisconnect:&lastDisconnect + forStream:self->xmppStream]; result = [self canResumeStreamWithResumptionId:resumptionId timeout:timeout lastDisconnect:lastDisconnect]; }}; @@ -477,16 +461,16 @@ - (void)sendResumeRequestWithResumptionId:(NSString *)resumptionId // State transition cleanup - [unackedByServer removeAllObjects]; - unackedByServer_lastRequestOffset = 0; + [self->unackedByServer removeAllObjects]; + self->unackedByServer_lastRequestOffset = 0; - [unackedByClient removeAllObjects]; - unackedByClient_lastAckOffset = 0; + [self->unackedByClient removeAllObjects]; + self->unackedByClient_lastAckOffset = 0; - unprocessedReceivedAcks = nil; + self->unprocessedReceivedAcks = nil; - pendingHandledStanzaIds = nil; - outstandingStanzaIds = 0; + self->pendingHandledStanzaIds = nil; + self->outstandingStanzaIds = 0; // Restore our state from the last stream @@ -494,20 +478,20 @@ - (void)sendResumeRequestWithResumptionId:(NSString *)resumptionId uint32_t newLastHandledByServer = 0; NSArray *pendingOutgoingStanzas = nil; - [storage getLastHandledByClient:&newLastHandledByClient - lastHandledByServer:&newLastHandledByServer - pendingOutgoingStanzas:&pendingOutgoingStanzas - forStream:xmppStream]; + [self->storage getLastHandledByClient:&newLastHandledByClient + lastHandledByServer:&newLastHandledByServer + pendingOutgoingStanzas:&pendingOutgoingStanzas + forStream:self->xmppStream]; - lastHandledByClient = newLastHandledByClient; - lastHandledByServer = newLastHandledByServer; + self->lastHandledByClient = newLastHandledByClient; + self->lastHandledByServer = newLastHandledByServer; if ([pendingOutgoingStanzas count] > 0) { - prev_unackedByServer = [[NSMutableArray alloc] initWithArray:pendingOutgoingStanzas copyItems:YES]; + self->prev_unackedByServer = [[NSMutableArray alloc] initWithArray:pendingOutgoingStanzas copyItems:YES]; } XMPPLogVerbose(@"%@: Attempting to resume: lastHandledByClient(%u) lastHandledByServer(%u)", - THIS_FILE, lastHandledByClient, lastHandledByServer); + THIS_FILE, self->lastHandledByClient, self->lastHandledByServer); // Send the resume stanza: // @@ -515,11 +499,11 @@ - (void)sendResumeRequestWithResumptionId:(NSString *)resumptionId NSXMLElement *resume = [NSXMLElement elementWithName:@"resume" xmlns:XMLNS_STREAM_MANAGEMENT]; [resume addAttributeWithName:@"previd" stringValue:resumptionId]; - [resume addAttributeWithName:@"h" stringValue:[NSString stringWithFormat:@"%u", lastHandledByClient]]; + [resume addAttributeWithName:@"h" stringValue:[NSString stringWithFormat:@"%u", self->lastHandledByClient]]; - [xmppStream sendBindElement:resume]; + [self->xmppStream sendBindElement:resume]; - didAttemptResume = YES; + self->didAttemptResume = YES; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -537,57 +521,61 @@ - (void)processResumed:(NSXMLElement *)resumed dispatch_block_t block = ^{ @autoreleasepool { - uint32_t h = [resumed attributeUInt32ValueForName:@"h" withDefaultValue:lastHandledByServer]; + uint32_t h = [resumed attributeUInt32ValueForName:@"h" withDefaultValue:self->lastHandledByServer]; uint32_t diff; - if (h >= lastHandledByServer) - diff = h - lastHandledByServer; + if (h >= self->lastHandledByServer) + diff = h - self->lastHandledByServer; else - diff = (UINT32_MAX - lastHandledByServer) + h; + diff = (UINT32_MAX - self->lastHandledByServer) + h; // IMPORTATNT: // This code path uses prev_unackedByServer (NOT unackedByServer). // This is because the ack has to do with stanzas sent from the previous connection. - if (diff > [prev_unackedByServer count]) + if (diff > [self->prev_unackedByServer count]) { XMPPLogWarn(@"Unexpected h value from resume: lastH=%lu, newH=%lu, numPendingStanzas=%lu", - (unsigned long)lastHandledByServer, - (unsigned long)h, - (unsigned long)[prev_unackedByServer count]); + (unsigned long)self->lastHandledByServer, + (unsigned long)h, + (unsigned long)[self->prev_unackedByServer count]); - diff = (uint32_t)[prev_unackedByServer count]; + diff = (uint32_t)[self->prev_unackedByServer count]; } NSMutableArray *stanzaIds = [NSMutableArray arrayWithCapacity:(NSUInteger)diff]; for (uint32_t i = 0; i < diff; i++) { - XMPPStreamManagementOutgoingStanza *outgoingStanza = [prev_unackedByServer objectAtIndex:(NSUInteger)i]; + XMPPStreamManagementOutgoingStanza *outgoingStanza = self->prev_unackedByServer[(NSUInteger) i]; if (outgoingStanza.stanzaId) { [stanzaIds addObject:outgoingStanza.stanzaId]; } } - lastHandledByServer = h; + self->lastHandledByServer = h; - XMPPLogVerbose(@"%@: processResumed: lastHandledByServer(%u)", THIS_FILE, lastHandledByServer); + XMPPLogVerbose(@"%@: processResumed: lastHandledByServer(%u)", THIS_FILE, self->lastHandledByServer); - isStarted = YES; - didResume = YES; + self->isStarted = YES; + self->didResume = YES; - prev_unackedByServer = nil; + self->prev_unackedByServer = nil; - resume_response = resumed; - resume_stanzaIds = [stanzaIds copy]; + self->resume_response = resumed; + self->resume_stanzaIds = [stanzaIds copy]; // Update storage - [storage setLastDisconnect:[NSDate date] - lastHandledByServer:lastHandledByServer - pendingOutgoingStanzas:nil - forStream:xmppStream]; + [self->storage setLastDisconnect:[NSDate date] + lastHandledByServer:self->lastHandledByServer + pendingOutgoingStanzas:nil + forStream:self->xmppStream]; + + // Notify delegate + + [self->multicastDelegate xmppStreamManagement:self didReceiveAckForStanzaIds:stanzaIds]; }}; if (dispatch_get_specific(moduleQueueTag)) @@ -610,7 +598,7 @@ - (BOOL)didResume __block BOOL result = NO; dispatch_block_t block = ^{ - result = didResume; + result = self->didResume; }; if (dispatch_get_specific(moduleQueueTag)) @@ -644,8 +632,8 @@ - (BOOL)didResume * YES if the stream was resumed. * NO otherwise. **/ -- (BOOL)didResumeWithAckedStanzaIds:(NSArray **)stanzaIdsPtr - serverResponse:(NSXMLElement **)responsePtr +- (BOOL)didResumeWithAckedStanzaIds:(NSArray * _Nullable * _Nullable)stanzaIdsPtr + serverResponse:(NSXMLElement * _Nullable * _Nullable)responsePtr { __block BOOL result = NO; __block NSArray *stanzaIds = nil; @@ -653,9 +641,9 @@ - (BOOL)didResumeWithAckedStanzaIds:(NSArray **)stanzaIdsPtr dispatch_block_t block = ^{ - result = didResume; - stanzaIds = resume_stanzaIds; - response = resume_response; + result = self->didResume; + stanzaIds = self->resume_stanzaIds; + response = self->resume_response; }; if (dispatch_get_specific(moduleQueueTag)) @@ -680,11 +668,11 @@ - (BOOL)didResumeWithAckedStanzaIds:(NSArray **)stanzaIdsPtr * this method should return XMPP_BIND_FAIL and set an appropriate error message. * * If binding isn't needed (for example, because custom SASL authentication already handled it), - * this method should return XMPP_BIND_SUCCESS. + * this method should return XMPPBindResultSuccess. * In this case, xmppStream will immediately move to its post-binding operations. * * Otherwise this method should send whatever stanzas are needed to begin the binding process. - * And then return XMPP_BIND_CONTINUE. + * And then return XMPPBindResultContinue. * * This method is called by automatically XMPPStream. * You MUST NOT invoke this method manually. @@ -707,19 +695,19 @@ - (XMPPBindResult)start:(NSError **)errPtr if (![self canResumeStreamWithResumptionId:resumptionId timeout:timeout lastDisconnect:lastDisconnect]) { - return XMPP_BIND_FAIL_FALLBACK; + return XMPPBindResultFailFallback; } // Start the resume proces [self sendResumeRequestWithResumptionId:resumptionId]; - return XMPP_BIND_CONTINUE; + return XMPPBindResultContinue; } /** * After the custom binding process has started, all incoming xmpp stanzas are routed to this method. * The method should process the stanza as appropriate, and return the coresponding result. - * If the process is not yet complete, it should return XMPP_BIND_CONTINUE, + * If the process is not yet complete, it should return XMPPBindResultContinue, * meaning the xmpp stream will continue to forward all incoming xmpp stanzas to this method. * * This method is called automatically by XMPPStream. @@ -735,7 +723,7 @@ - (XMPPBindResult)handleBind:(NSXMLElement *)element withError:(NSError **)errPt { [self processResumed:element]; - return XMPP_BIND_SUCCESS; + return XMPPBindResultSuccess; } else { @@ -745,13 +733,13 @@ - (XMPPBindResult)handleBind:(NSXMLElement *)element withError:(NSError **)errPt dispatch_async(moduleQueue, ^{ @autoreleasepool { - didResume = NO; - resume_response = element; + self->didResume = NO; + self->resume_response = element; - prev_unackedByServer = nil; + self->prev_unackedByServer = nil; }}); - return XMPP_BIND_FAIL_FALLBACK; + return XMPPBindResultFailFallback; } } @@ -799,7 +787,7 @@ - (void)requestAck dispatch_block_t block = ^{ @autoreleasepool{ - if (isStarted || enableQueued || enableSent) + if (self->isStarted || self->enableQueued || self->enableSent) { [self _requestAck]; } @@ -932,7 +920,7 @@ - (void)processSentElement:(XMPPElement *)element stanzaId = [element elementID]; } - dispatch_async(moduleQueue, ^{ @autoreleasepool{ + dispatch_async(self->moduleQueue, ^{ @autoreleasepool{ // Set the stanzaId. stanza.stanzaId = stanzaId; @@ -945,13 +933,13 @@ - (void)processSentElement:(XMPPElement *)element BOOL dequeuedPendingAck = NO; - while ([unprocessedReceivedAcks count] > 0) + while ([self->unprocessedReceivedAcks count] > 0) { - NSXMLElement *ack = [unprocessedReceivedAcks objectAtIndex:0]; + NSXMLElement *ack = self->unprocessedReceivedAcks[0]; if ([self processReceivedAck:ack]) { - [unprocessedReceivedAcks removeObjectAtIndex:0]; + [self->unprocessedReceivedAcks removeObjectAtIndex:0]; dequeuedPendingAck = YES; } else @@ -1029,7 +1017,7 @@ - (BOOL)processReceivedAck:(NSXMLElement *)ack for (uint32_t i = 0; i < diff; i++) { - XMPPStreamManagementOutgoingStanza *outgoingStanza = [unackedByServer objectAtIndex:(NSUInteger)i]; + XMPPStreamManagementOutgoingStanza *outgoingStanza = unackedByServer[(NSUInteger) i]; if ([outgoingStanza awaitingStanzaId]) { @@ -1127,7 +1115,7 @@ - (void)sendAck dispatch_block_t block = ^{ @autoreleasepool{ - if (isStarted) + if (self->isStarted) { [self _sendAck]; } @@ -1340,7 +1328,7 @@ - (void)markHandledStanzaId:(id)stanzaId BOOL found = NO; - for (XMPPStreamManagementIncomingStanza *stanza in unackedByClient) + for (XMPPStreamManagementIncomingStanza *stanza in self->unackedByClient) { if (stanza.isHandled) { @@ -1372,12 +1360,12 @@ - (void)markHandledStanzaId:(id)stanzaId // to actually "handle" the element. So we have this odd edge case, // which we handle by queuing up the stanzaId for later processing. - if (outstandingStanzaIds > 0) + if (self->outstandingStanzaIds > 0) { - if (pendingHandledStanzaIds == nil) - pendingHandledStanzaIds = [[NSMutableArray alloc] init]; + if (self->pendingHandledStanzaIds == nil) + self->pendingHandledStanzaIds = [[NSMutableArray alloc] init]; - [pendingHandledStanzaIds addObject:stanzaId]; + [self->pendingHandledStanzaIds addObject:stanzaId]; } } }}; @@ -1454,7 +1442,7 @@ - (void)processReceivedElement:(XMPPElement *)element }}); } - dispatch_async(moduleQueue, ^{ @autoreleasepool + dispatch_async(self->moduleQueue, ^{ @autoreleasepool { if (isHandled) { @@ -1466,14 +1454,14 @@ - (void)processReceivedElement:(XMPPElement *)element // Check for edge case: // - stanzaId was marked as handled before we figured out what the stanzaId was - if ([pendingHandledStanzaIds count] > 0) + if ([self->pendingHandledStanzaIds count] > 0) { NSUInteger i = 0; - for (id pendingStanzaId in pendingHandledStanzaIds) + for (id pendingStanzaId in self->pendingHandledStanzaIds) { if ([pendingStanzaId isEqual:stanzaId]) { - [pendingHandledStanzaIds removeObjectAtIndex:i]; + [self->pendingHandledStanzaIds removeObjectAtIndex:i]; stanza.isHandled = YES; break; @@ -1486,8 +1474,8 @@ - (void)processReceivedElement:(XMPPElement *)element // Defensive programming. // Don't let this array grow infinitely big (if markHandledStanzaId is being invoked incorrectly). - if (--outstandingStanzaIds == 0) { - [pendingHandledStanzaIds removeAllObjects]; + if (--self->outstandingStanzaIds == 0) { + [self->pendingHandledStanzaIds removeAllObjects]; } if (stanza.isHandled) diff --git a/Extensions/XEP-0199/XMPPAutoPing.m b/Extensions/XEP-0199/XMPPAutoPing.m index 593615816a..4d3820e4f2 100644 --- a/Extensions/XEP-0199/XMPPAutoPing.m +++ b/Extensions/XEP-0199/XMPPAutoPing.m @@ -65,10 +65,10 @@ - (void)deactivate [self stopPingIntervalTimer]; - lastReceiveTime = 0; - awaitingPingResponse = NO; + self->lastReceiveTime = 0; + self->awaitingPingResponse = NO; - [xmppPing deactivate]; + [self->xmppPing deactivate]; [super deactivate]; }}; @@ -103,7 +103,7 @@ - (NSTimeInterval)pingInterval __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ - result = pingInterval; + result = self->pingInterval; }); return result; } @@ -113,19 +113,19 @@ - (void)setPingInterval:(NSTimeInterval)interval { dispatch_block_t block = ^{ - if (pingInterval != interval) + if (self->pingInterval != interval) { - pingInterval = interval; + self->pingInterval = interval; // Update the pingTimer. // // Depending on new value and current state of the pingTimer, // this may mean starting, stoping, or simply updating the timer. - if (pingInterval > 0) + if (self->pingInterval > 0) { // Remember: Only start the pinger after the xmpp stream is up and authenticated - if ([xmppStream isAuthenticated]) + if ([self->xmppStream isAuthenticated]) [self startPingIntervalTimer]; } else @@ -152,7 +152,7 @@ - (NSTimeInterval)pingTimeout __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ - result = pingTimeout; + result = self->pingTimeout; }); return result; } @@ -162,9 +162,9 @@ - (void)setPingTimeout:(NSTimeInterval)timeout { dispatch_block_t block = ^{ - if (pingTimeout != timeout) + if (self->pingTimeout != timeout) { - pingTimeout = timeout; + self->pingTimeout = timeout; } }; @@ -185,7 +185,7 @@ - (XMPPJID *)targetJID __block XMPPJID *result; dispatch_sync(moduleQueue, ^{ - result = targetJID; + result = self->targetJID; }); return result; } @@ -195,11 +195,11 @@ - (void)setTargetJID:(XMPPJID *)jid { dispatch_block_t block = ^{ - if (![targetJID isEqualToJID:jid]) + if (![self->targetJID isEqualToJID:jid]) { - targetJID = jid; + self->targetJID = jid; - targetJIDStr = [targetJID full]; + self->targetJIDStr = [self->targetJID full]; } }; @@ -220,7 +220,7 @@ - (NSTimeInterval)lastReceiveTime __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ - result = lastReceiveTime; + result = self->lastReceiveTime; }); return result; } diff --git a/Extensions/XEP-0199/XMPPPing.m b/Extensions/XEP-0199/XMPPPing.m index e733ab73d7..73c21e68a0 100644 --- a/Extensions/XEP-0199/XMPPPing.m +++ b/Extensions/XEP-0199/XMPPPing.m @@ -68,8 +68,8 @@ - (void)deactivate dispatch_block_t block = ^{ @autoreleasepool { - [pingTracker removeAllIDs]; - pingTracker = nil; + [self->pingTracker removeAllIDs]; + self->pingTracker = nil; }}; @@ -93,7 +93,7 @@ - (BOOL)respondsToQueries __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = respondsToQueries; + result = self->respondsToQueries; }); return result; } @@ -103,14 +103,14 @@ - (void)setRespondsToQueries:(BOOL)flag { dispatch_block_t block = ^{ - if (respondsToQueries != flag) + if (self->respondsToQueries != flag) { - respondsToQueries = flag; + self->respondsToQueries = flag; #ifdef _XMPP_CAPABILITIES_H @autoreleasepool { // Capabilities may have changed, need to notify others. - [xmppStream resendMyPresence]; + [self->xmppStream resendMyPresence]; } #endif } @@ -137,7 +137,7 @@ - (NSString *)generatePingIDWithTimeout:(NSTimeInterval)timeout selector:@selector(handlePong:withInfo:) timeout:timeout]; - [pingTracker addID:pingID trackingInfo:pingInfo]; + [self->pingTracker addID:pingID trackingInfo:pingInfo]; }}); diff --git a/Extensions/XEP-0202/XMPPAutoTime.m b/Extensions/XEP-0202/XMPPAutoTime.m index 86037d6772..e6bb648c70 100644 --- a/Extensions/XEP-0202/XMPPAutoTime.m +++ b/Extensions/XEP-0202/XMPPAutoTime.m @@ -70,8 +70,8 @@ - (void)deactivate [self stopRecalibrationTimer]; - [xmppTime deactivate]; - awaitingQueryResponse = NO; + [self->xmppTime deactivate]; + self->awaitingQueryResponse = NO; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -110,7 +110,7 @@ - (NSTimeInterval)recalibrationInterval __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ - result = recalibrationInterval; + result = self->recalibrationInterval; }); return result; } @@ -120,19 +120,19 @@ - (void)setRecalibrationInterval:(NSTimeInterval)interval { dispatch_block_t block = ^{ - if (recalibrationInterval != interval) + if (self->recalibrationInterval != interval) { - recalibrationInterval = interval; + self->recalibrationInterval = interval; // Update the recalibrationTimer. // // Depending on new value and current state of the recalibrationTimer, // this may mean starting, stoping, or simply updating the timer. - if (recalibrationInterval > 0) + if (self->recalibrationInterval > 0) { // Remember: Only start the timer after the xmpp stream is up and authenticated - if ([xmppStream isAuthenticated]) + if ([self->xmppStream isAuthenticated]) [self startRecalibrationTimer]; } else @@ -159,7 +159,7 @@ - (XMPPJID *)targetJID __block XMPPJID *result; dispatch_sync(moduleQueue, ^{ - result = targetJID; + result = self->targetJID; }); return result; } @@ -169,9 +169,9 @@ - (void)setTargetJID:(XMPPJID *)jid { dispatch_block_t block = ^{ - if (![targetJID isEqualToJID:jid]) + if (![self->targetJID isEqualToJID:jid]) { - targetJID = jid; + self->targetJID = jid; } }; @@ -192,7 +192,7 @@ - (NSTimeInterval)timeDifference __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ - result = timeDifference; + result = self->timeDifference; }); return result; @@ -210,7 +210,7 @@ - (NSDate *)date __block NSDate *result; dispatch_sync(moduleQueue, ^{ - result = [[NSDate date] dateByAddingTimeInterval:-timeDifference]; + result = [[NSDate date] dateByAddingTimeInterval:-self->timeDifference]; }); return result; @@ -228,7 +228,7 @@ - (dispatch_time_t)lastCalibrationTime __block dispatch_time_t result; dispatch_sync(moduleQueue, ^{ - result = lastCalibrationTime; + result = self->lastCalibrationTime; }); return result; @@ -276,10 +276,10 @@ - (void)systemClockDidChange:(NSNotification *)notification // Calculate system clock change - NSDate *oldSysTime = systemUptimeChecked; + NSDate *oldSysTime = self->systemUptimeChecked; NSDate *newSysTime = now; - NSTimeInterval oldSysUptime = systemUptime; + NSTimeInterval oldSysUptime = self->systemUptime; NSTimeInterval newSysUptime = sysUptime; NSTimeInterval sysTimeDiff = [newSysTime timeIntervalSinceDate:oldSysTime]; @@ -289,13 +289,13 @@ - (void)systemClockDidChange:(NSNotification *)notification // Modify timeDifference & notify delegate - timeDifference += sysClockChange; - [multicastDelegate xmppAutoTime:self didUpdateTimeDifference:timeDifference]; + self->timeDifference += sysClockChange; + [self->multicastDelegate xmppAutoTime:self didUpdateTimeDifference:self->timeDifference]; // Dont forget to update our variables self.systemUptimeChecked = now; - systemUptime = sysUptime; + self->systemUptime = sysUptime; }}); } diff --git a/Extensions/XEP-0202/XMPPTime.m b/Extensions/XEP-0202/XMPPTime.m index 3e09cd5b6f..96cefbb1a9 100644 --- a/Extensions/XEP-0202/XMPPTime.m +++ b/Extensions/XEP-0202/XMPPTime.m @@ -68,8 +68,8 @@ - (void)deactivate dispatch_block_t block = ^{ @autoreleasepool { - [queryTracker removeAllIDs]; - queryTracker = nil; + [self->queryTracker removeAllIDs]; + self->queryTracker = nil; }}; @@ -93,7 +93,7 @@ - (BOOL)respondsToQueries __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = respondsToQueries; + result = self->respondsToQueries; }); return result; } @@ -103,14 +103,14 @@ - (void)setRespondsToQueries:(BOOL)flag { dispatch_block_t block = ^{ - if (respondsToQueries != flag) + if (self->respondsToQueries != flag) { - respondsToQueries = flag; + self->respondsToQueries = flag; #ifdef _XMPP_CAPABILITIES_H @autoreleasepool { // Capabilities may have changed, need to notify others. - [xmppStream resendMyPresence]; + [self->xmppStream resendMyPresence]; } #endif } @@ -138,7 +138,7 @@ - (NSString *)generateQueryIDWithTimeout:(NSTimeInterval)timeout selector:@selector(handleResponse:withInfo:) timeout:timeout]; - [queryTracker addID:queryID trackingInfo:queryInfo]; + [self->queryTracker addID:queryID trackingInfo:queryInfo]; }}); diff --git a/Extensions/XEP-0203/NSXMLElement+XEP_0203.h b/Extensions/XEP-0203/NSXMLElement+XEP_0203.h index 27db9235b4..84fd413ba9 100644 --- a/Extensions/XEP-0203/NSXMLElement+XEP_0203.h +++ b/Extensions/XEP-0203/NSXMLElement+XEP_0203.h @@ -1,12 +1,13 @@ #import -#if TARGET_OS_IPHONE -#import "DDXML.h" -#endif +@import KissXML; +@class XMPPJID; @interface NSXMLElement (XEP_0203) -- (BOOL)wasDelayed; -- (NSDate *)delayedDeliveryDate; +@property (nonatomic, readonly) BOOL wasDelayed; +@property (nonatomic, readonly, nullable) NSDate *delayedDeliveryDate; +@property (nonatomic, readonly, nullable) XMPPJID *delayedDeliveryFrom; +@property (nonatomic, readonly, nullable) NSString *delayedDeliveryReasonDescription; @end diff --git a/Extensions/XEP-0203/NSXMLElement+XEP_0203.m b/Extensions/XEP-0203/NSXMLElement+XEP_0203.m index ee404d3326..82b0b371e6 100644 --- a/Extensions/XEP-0203/NSXMLElement+XEP_0203.m +++ b/Extensions/XEP-0203/NSXMLElement+XEP_0203.m @@ -1,6 +1,7 @@ #import "NSXMLElement+XEP_0203.h" #import "XMPPDateTimeProfiles.h" #import "NSXMLElement+XMPP.h" +#import "XMPPJID.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). @@ -10,27 +11,11 @@ @implementation NSXMLElement (XEP_0203) - (BOOL)wasDelayed { - NSXMLElement *delay; - - delay = [self elementForName:@"delay" xmlns:@"urn:xmpp:delay"]; - if (delay) - { - return YES; - } - - delay = [self elementForName:@"x" xmlns:@"jabber:x:delay"]; - if (delay) - { - return YES; - } - - return NO; + return [self anyDelayedDeliveryChildElement] != nil; } - (NSDate *)delayedDeliveryDate { - NSXMLElement *delay; - // From XEP-0203 (Delayed Delivery) // // - delay = [self elementForName:@"x" xmlns:@"jabber:x:delay"]; - if (delay) + NSXMLElement *legacyDelay = [self legacyDelayedDeliveryChildElement]; + if (legacyDelay) { NSDate *stamp; - NSString *stampValue = [delay attributeStringValueForName:@"stamp"]; + NSString *stampValue = [legacyDelay attributeStringValueForName:@"stamp"]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; @@ -81,4 +66,30 @@ - (NSDate *)delayedDeliveryDate return nil; } +- (XMPPJID *)delayedDeliveryFrom +{ + NSString *delayedDeliveryFromString = [[self anyDelayedDeliveryChildElement] attributeStringValueForName:@"from"]; + return delayedDeliveryFromString ? [XMPPJID jidWithString:delayedDeliveryFromString] : nil; +} + +- (NSString *)delayedDeliveryReasonDescription +{ + return [self anyDelayedDeliveryChildElement].stringValue; +} + +- (NSXMLElement *)delayedDeliveryChildElement +{ + return [self elementForName:@"delay" xmlns:@"urn:xmpp:delay"]; +} + +- (NSXMLElement *)legacyDelayedDeliveryChildElement +{ + return [self elementForName:@"x" xmlns:@"jabber:x:delay"]; +} + +- (NSXMLElement *)anyDelayedDeliveryChildElement +{ + return [self delayedDeliveryChildElement] ?: [self legacyDelayedDeliveryChildElement]; +} + @end diff --git a/Extensions/XEP-0203/XMPPDelayedDelivery.h b/Extensions/XEP-0203/XMPPDelayedDelivery.h new file mode 100644 index 0000000000..29d5240dd0 --- /dev/null +++ b/Extensions/XEP-0203/XMPPDelayedDelivery.h @@ -0,0 +1,25 @@ +#import "XMPP.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A module for processing XEP-0203 Delayed Delivery information in incoming XMPP stanzas. +@interface XMPPDelayedDelivery : XMPPModule + +@end + +/// A protocol defining @c XMPPDelayedDelivery module delegate API. +@protocol XMPPDelayedDeliveryDelegate + +@optional + +/// Notifies the delegate that a delayed delivery message has been received in the stream. +- (void)xmppDelayedDelivery:(XMPPDelayedDelivery *)xmppDelayedDelivery + didReceiveDelayedMessage:(XMPPMessage *)delayedMessage; + +/// Notifies the delegate that a delayed delivery presence has been received in the stream. +- (void)xmppDelayedDelivery:(XMPPDelayedDelivery *)xmppDelayedDelivery + didReceiveDelayedPresence:(XMPPPresence *)delayedPresence; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0203/XMPPDelayedDelivery.m b/Extensions/XEP-0203/XMPPDelayedDelivery.m new file mode 100644 index 0000000000..7831754150 --- /dev/null +++ b/Extensions/XEP-0203/XMPPDelayedDelivery.m @@ -0,0 +1,57 @@ +#import "XMPPDelayedDelivery.h" +#import "XMPPLogging.h" +#import "NSXMLElement+XEP_0203.h" + +// Log levels: off, error, warn, info, verbose +// Log flags: trace +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +@implementation XMPPDelayedDelivery + +- (void)didActivate +{ + XMPPLogTrace(); +} + +- (void)willDeactivate +{ + XMPPLogTrace(); +} + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message +{ + XMPPLogTrace(); + + if (![message wasDelayed]) { + return; + } + + XMPPLogInfo(@"Received delayed delivery message with date: %@, origin: %@, reason description: %@", + [message delayedDeliveryDate], + [message delayedDeliveryFrom] ?: @"unspecified", + [message delayedDeliveryReasonDescription] ?: @"unspecified"); + + [multicastDelegate xmppDelayedDelivery:self didReceiveDelayedMessage:message]; +} + +- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence +{ + XMPPLogTrace(); + + if (![presence wasDelayed]) { + return; + } + + XMPPLogInfo(@"Received delayed delivery presence with date: %@, origin: %@, reason description: %@", + [presence delayedDeliveryDate], + [presence delayedDeliveryFrom] ?: @"unspecified", + [presence delayedDeliveryReasonDescription] ?: @"unspecified"); + + [multicastDelegate xmppDelayedDelivery:self didReceiveDelayedPresence:presence]; +} + +@end diff --git a/Extensions/XEP-0223/XEP_0223.h b/Extensions/XEP-0223/XEP_0223.h index aa0670fc4b..6d2f4b8716 100644 --- a/Extensions/XEP-0223/XEP_0223.h +++ b/Extensions/XEP-0223/XEP_0223.h @@ -9,13 +9,14 @@ #import - +NS_ASSUME_NONNULL_BEGIN @interface XEP_0223 : NSObject /** * This method returns the recommended configuration options to configure a pubsub node for storing private data. * It may be passed directly to the publishToNoe:::: method of XMPPPubSub. **/ -+ (NSDictionary *)privateStoragePubSubOptions; ++ (NSDictionary *)privateStoragePubSubOptions; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0224/XMPPAttentionModule.h b/Extensions/XEP-0224/XMPPAttentionModule.h index c003ee7916..b8f26a2bfd 100644 --- a/Extensions/XEP-0224/XMPPAttentionModule.h +++ b/Extensions/XEP-0224/XMPPAttentionModule.h @@ -3,9 +3,8 @@ #define _XMPP_ATTENTION_MODULE_H -@interface XMPPAttentionModule : XMPPModule { - BOOL respondsToQueries; -} +NS_ASSUME_NONNULL_BEGIN +@interface XMPPAttentionModule : XMPPModule /** * Whether or not the module should respond to incoming attention request queries. @@ -22,3 +21,4 @@ @optional - (void)xmppAttention:(XMPPAttentionModule *)sender didReceiveAttentionHeadlineMessage:(XMPPMessage *)attentionRequest; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0224/XMPPAttentionModule.m b/Extensions/XEP-0224/XMPPAttentionModule.m index 439152ef2c..a31f3e6a3b 100644 --- a/Extensions/XEP-0224/XMPPAttentionModule.m +++ b/Extensions/XEP-0224/XMPPAttentionModule.m @@ -1,10 +1,12 @@ #import "XMPPAttentionModule.h" +#import "XMPPFramework.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif @implementation XMPPAttentionModule +@synthesize respondsToQueries; - (id)init { @@ -53,7 +55,7 @@ - (BOOL)respondsToQueries __block BOOL result; dispatch_sync(moduleQueue, ^{ - result = respondsToQueries; + result = self->respondsToQueries; }); return result; } @@ -63,14 +65,14 @@ - (void)setRespondsToQueries:(BOOL)flag { dispatch_block_t block = ^{ - if (respondsToQueries != flag) + if (self->respondsToQueries != flag) { - respondsToQueries = flag; + self->respondsToQueries = flag; #ifdef _XMPP_CAPABILITIES_H @autoreleasepool { // Capabilities may have changed, need to notify others. - [xmppStream resendMyPresence]; + [self->xmppStream resendMyPresence]; } #endif } diff --git a/Extensions/XEP-0224/XMPPMessage+XEP_0224.h b/Extensions/XEP-0224/XMPPMessage+XEP_0224.h index 45e81a287e..38e0ef9b8f 100644 --- a/Extensions/XEP-0224/XMPPMessage+XEP_0224.h +++ b/Extensions/XEP-0224/XMPPMessage+XEP_0224.h @@ -1,8 +1,10 @@ #import "XMPPMessage.h" -#define XMLNS_ATTENTION @"urn:xmpp:attention:0" + +/** "urn:xmpp:attention:0" */ +FOUNDATION_EXPORT NSString *const XMLNS_ATTENTION; @interface XMPPMessage (XEP_0224) -- (BOOL)isHeadLineMessage; -- (BOOL)isAttentionMessage; -- (BOOL)isAttentionMessageWithBody; +@property (nonatomic, readonly) BOOL isHeadLineMessage; +@property (nonatomic, readonly) BOOL isAttentionMessage; +@property (nonatomic, readonly) BOOL isAttentionMessageWithBody; @end diff --git a/Extensions/XEP-0224/XMPPMessage+XEP_0224.m b/Extensions/XEP-0224/XMPPMessage+XEP_0224.m index 91bc941be2..2ba9744ffe 100644 --- a/Extensions/XEP-0224/XMPPMessage+XEP_0224.m +++ b/Extensions/XEP-0224/XMPPMessage+XEP_0224.m @@ -1,6 +1,8 @@ #import "XMPPMessage+XEP_0224.h" #import "NSXMLElement+XMPP.h" +NSString *const XMLNS_ATTENTION = @"urn:xmpp:attention:0"; + @implementation XMPPMessage (XEP_0224) - (BOOL)isHeadLineMessage { diff --git a/Extensions/XEP-0280/XMPPMessage+XEP_0280.h b/Extensions/XEP-0280/XMPPMessage+XEP_0280.h index 7f7696579b..7cffcbe186 100644 --- a/Extensions/XEP-0280/XMPPMessage+XEP_0280.h +++ b/Extensions/XEP-0280/XMPPMessage+XEP_0280.h @@ -1,19 +1,21 @@ #import "XMPPMessage.h" @class XMPPJID; +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (XEP_0280) -- (NSXMLElement *)receivedMessageCarbon; -- (NSXMLElement *)sentMessageCarbon; +@property (nonatomic, readonly, nullable) NSXMLElement *receivedMessageCarbon; +@property (nonatomic, readonly, nullable) NSXMLElement *sentMessageCarbon; -- (BOOL)isMessageCarbon; -- (BOOL)isReceivedMessageCarbon; -- (BOOL)isSentMessageCarbon; -- (BOOL)isTrustedMessageCarbon; +@property (nonatomic, readonly) BOOL isMessageCarbon; +@property (nonatomic, readonly) BOOL isReceivedMessageCarbon; +@property (nonatomic, readonly) BOOL isSentMessageCarbon; +@property (nonatomic, readonly) BOOL isTrustedMessageCarbon; - (BOOL)isTrustedMessageCarbonForMyJID:(XMPPJID *)jid; -- (XMPPMessage *)messageCarbonForwardedMessage; +@property (nonatomic, readonly, nullable) XMPPMessage *messageCarbonForwardedMessage; - (void)addPrivateMessageCarbons; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0280/XMPPMessage+XEP_0280.m b/Extensions/XEP-0280/XMPPMessage+XEP_0280.m index 4229e3f59b..fd95b98bbb 100644 --- a/Extensions/XEP-0280/XMPPMessage+XEP_0280.m +++ b/Extensions/XEP-0280/XMPPMessage+XEP_0280.m @@ -62,6 +62,9 @@ - (BOOL)isTrustedMessageCarbon { BOOL isTrustedMessageCarbon = NO; XMPPMessage *messageCarbonForwardedMessage = [self messageCarbonForwardedMessage]; + if (!messageCarbonForwardedMessage) { + return NO; + } if([self isSentMessageCarbon]) { diff --git a/Extensions/XEP-0280/XMPPMessageCarbons.h b/Extensions/XEP-0280/XMPPMessageCarbons.h index 66960853d9..1de4459d67 100644 --- a/Extensions/XEP-0280/XMPPMessageCarbons.h +++ b/Extensions/XEP-0280/XMPPMessageCarbons.h @@ -4,14 +4,8 @@ #define _XMPP_MESSAGE_CARBONS_H +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessageCarbons : XMPPModule -{ - BOOL autoEnableMessageCarbons; - BOOL allowsUntrustedMessageCarbons; - BOOL messageCarbonsEnabled; - - XMPPIDTracker *xmppIDTracker; -} /** * Wether or not to automatically enable Message Carbons. @@ -26,7 +20,7 @@ * @see enableMessageCarbons * @see disableMessageCarbons **/ -@property (assign, getter = isMessageCarbonsEnabled,readonly) BOOL messageCarbonsEnabled; +@property (atomic, readonly) BOOL isMessageCarbonsEnabled; /** * Whether Message Carbons are validated before calling the delegate methods. @@ -57,9 +51,11 @@ @end @protocol XMPPMessageCarbonsDelegate +@optional - (void)xmppMessageCarbons:(XMPPMessageCarbons *)xmppMessageCarbons willReceiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing; - (void)xmppMessageCarbons:(XMPPMessageCarbons *)xmppMessageCarbons didReceiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing; -@end \ No newline at end of file +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0280/XMPPMessageCarbons.m b/Extensions/XEP-0280/XMPPMessageCarbons.m index 6111bfa08c..c592f1376a 100644 --- a/Extensions/XEP-0280/XMPPMessageCarbons.m +++ b/Extensions/XEP-0280/XMPPMessageCarbons.m @@ -22,6 +22,16 @@ #define XMLNS_XMPP_MESSAGE_CARBONS @"urn:xmpp:carbons:2" +@interface XMPPMessageCarbons() +{ + BOOL autoEnableMessageCarbons; + BOOL allowsUntrustedMessageCarbons; + BOOL messageCarbonsEnabled; + + XMPPIDTracker *xmppIDTracker; +} +@end + @implementation XMPPMessageCarbons - (id)initWithDispatchQueue:(dispatch_queue_t)queue @@ -52,11 +62,11 @@ - (BOOL)activate:(XMPPStream *)aXmppStream - (void)deactivate { XMPPLogTrace(); - + dispatch_block_t block = ^{ @autoreleasepool { - [xmppIDTracker removeAllIDs]; - xmppIDTracker = nil; + [self->xmppIDTracker removeAllIDs]; + self->xmppIDTracker = nil; }}; @@ -77,7 +87,7 @@ - (BOOL)autoEnableMessageCarbons __block BOOL result = NO; dispatch_block_t block = ^{ - result = autoEnableMessageCarbons; + result = self->autoEnableMessageCarbons; }; if (dispatch_get_specific(moduleQueueTag)) @@ -91,7 +101,7 @@ - (BOOL)autoEnableMessageCarbons - (void)setAutoEnableMessageCarbons:(BOOL)flag { dispatch_block_t block = ^{ - autoEnableMessageCarbons = flag; + self->autoEnableMessageCarbons = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -105,7 +115,7 @@ - (BOOL)isMessageCarbonsEnabled __block BOOL result = NO; dispatch_block_t block = ^{ - result = messageCarbonsEnabled; + result = self->messageCarbonsEnabled; }; if (dispatch_get_specific(moduleQueueTag)) @@ -121,7 +131,7 @@ - (BOOL)allowsUntrustedMessageCarbons __block BOOL result = NO; dispatch_block_t block = ^{ - result = allowsUntrustedMessageCarbons; + result = self->allowsUntrustedMessageCarbons; }; if (dispatch_get_specific(moduleQueueTag)) @@ -135,7 +145,7 @@ - (BOOL)allowsUntrustedMessageCarbons - (void)setAllowsUntrustedMessageCarbons:(BOOL)flag { dispatch_block_t block = ^{ - allowsUntrustedMessageCarbons = flag; + self->allowsUntrustedMessageCarbons = flag; }; if (dispatch_get_specific(moduleQueueTag)) @@ -148,7 +158,7 @@ - (void)enableMessageCarbons { dispatch_block_t block = ^{ - if(!messageCarbonsEnabled && [xmppIDTracker numberOfIDs] == 0) + if(!self->messageCarbonsEnabled && [self->xmppIDTracker numberOfIDs] == 0) { NSString *elementID = [XMPPStream generateUUID]; XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementID]; @@ -157,12 +167,12 @@ - (void)enableMessageCarbons NSXMLElement *enable = [NSXMLElement elementWithName:@"enable" xmlns:XMLNS_XMPP_MESSAGE_CARBONS]; [iq addChild:enable]; - [xmppIDTracker addElement:iq - target:self - selector:@selector(enableMessageCarbonsIQ:withInfo:) - timeout:XMPPIDTrackerTimeoutNone]; + [self->xmppIDTracker addElement:iq + target:self + selector:@selector(enableMessageCarbonsIQ:withInfo:) + timeout:XMPPIDTrackerTimeoutNone]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; } }; @@ -176,7 +186,7 @@ - (void)disableMessageCarbons { dispatch_block_t block = ^{ - if(messageCarbonsEnabled && [xmppIDTracker numberOfIDs] == 0) + if(self->messageCarbonsEnabled && [self->xmppIDTracker numberOfIDs] == 0) { NSString *elementID = [XMPPStream generateUUID]; @@ -186,12 +196,12 @@ - (void)disableMessageCarbons NSXMLElement *enable = [NSXMLElement elementWithName:@"disable" xmlns:XMLNS_XMPP_MESSAGE_CARBONS]; [iq addChild:enable]; - [xmppIDTracker addElement:iq - target:self - selector:@selector(disableMessageCarbonsIQ:withInfo:) - timeout:XMPPIDTrackerTimeoutNone]; + [self->xmppIDTracker addElement:iq + target:self + selector:@selector(disableMessageCarbonsIQ:withInfo:) + timeout:XMPPIDTrackerTimeoutNone]; - [xmppStream sendElement:iq]; + [self->xmppStream sendElement:iq]; } }; diff --git a/Extensions/XEP-0297/NSXMLElement+XEP_0297.h b/Extensions/XEP-0297/NSXMLElement+XEP_0297.h index cebec070b6..8f2d695a60 100644 --- a/Extensions/XEP-0297/NSXMLElement+XEP_0297.h +++ b/Extensions/XEP-0297/NSXMLElement+XEP_0297.h @@ -1,39 +1,39 @@ #import -#if TARGET_OS_IPHONE - #import "DDXML.h" -#endif +@import KissXML; @class XMPPIQ; @class XMPPMessage; @class XMPPPresence; +NS_ASSUME_NONNULL_BEGIN @interface NSXMLElement (XEP_0297) #pragma mark Forwarded Stanza -- (NSXMLElement *)forwardedStanza; +@property (nonatomic, readonly, nullable) NSXMLElement *forwardedStanza; -- (BOOL)hasForwardedStanza; +@property (nonatomic, readonly) BOOL hasForwardedStanza; -- (BOOL)isForwardedStanza; +@property (nonatomic, readonly) BOOL isForwardedStanza; #pragma mark Delayed Delivery Date -- (NSDate *)forwardedStanzaDelayedDeliveryDate; +@property (nonatomic, readonly, nullable) NSDate *forwardedStanzaDelayedDeliveryDate; #pragma mark XMPPElement -- (XMPPIQ *)forwardedIQ; +@property (nonatomic, readonly, nullable) XMPPIQ *forwardedIQ; -- (BOOL)hasForwardedIQ; +@property (nonatomic, readonly) BOOL hasForwardedIQ; -- (XMPPMessage *)forwardedMessage; +@property (nonatomic, readonly, nullable) XMPPMessage *forwardedMessage; -- (BOOL)hasForwardedMessage; +@property (nonatomic, readonly) BOOL hasForwardedMessage; -- (XMPPPresence *)forwardedPresence; +@property (nonatomic, readonly, nullable) XMPPPresence *forwardedPresence; -- (BOOL)hasForwardedPresence; +@property (nonatomic, readonly) BOOL hasForwardedPresence; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0308/XMPPMessage+XEP_0308.h b/Extensions/XEP-0308/XMPPMessage+XEP_0308.h index eec8267d10..1f385c192b 100644 --- a/Extensions/XEP-0308/XMPPMessage+XEP_0308.h +++ b/Extensions/XEP-0308/XMPPMessage+XEP_0308.h @@ -1,14 +1,16 @@ #import "XMPPMessage.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (XEP_0308) -- (BOOL)isMessageCorrection; +@property (nonatomic, readonly) BOOL isMessageCorrection; -- (NSString *)correctedMessageID; +@property (nonatomic, readonly, nullable) NSString *correctedMessageID; - (void)addMessageCorrectionWithID:(NSString *)messageCorrectionID; -- (XMPPMessage *)generateCorrectionMessageWithID:(NSString *)elementID; -- (XMPPMessage *)generateCorrectionMessageWithID:(NSString *)elementID body:(NSString *)body; +- (nullable XMPPMessage *)generateCorrectionMessageWithID:(nullable NSString *)elementID; +- (nullable XMPPMessage *)generateCorrectionMessageWithID:(nullable NSString *)elementID body:(NSString *)body; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0308/XMPPMessage+XEP_0308.m b/Extensions/XEP-0308/XMPPMessage+XEP_0308.m index d34c8db287..acad441762 100644 --- a/Extensions/XEP-0308/XMPPMessage+XEP_0308.m +++ b/Extensions/XEP-0308/XMPPMessage+XEP_0308.m @@ -29,7 +29,7 @@ - (NSString *)correctedMessageID - (void)addMessageCorrectionWithID:(NSString *)messageCorrectionID { - NSXMLElement *replace = [NSXMLElement elementWithName:NAME_XMPP_MESSAGE_CORRECT stringValue:XMLNS_XMPP_MESSAGE_CORRECT]; + NSXMLElement *replace = [NSXMLElement elementWithName:NAME_XMPP_MESSAGE_CORRECT xmlns:XMLNS_XMPP_MESSAGE_CORRECT]; [replace addAttributeWithName:@"id" stringValue:messageCorrectionID]; [self addChild:replace]; } @@ -77,7 +77,7 @@ - (XMPPMessage *)generateCorrectionMessageWithID:(NSString *)elementID body:(NSS [correctionMessage removeChildAtIndex:[[correctionMessage children] indexOfObject:bodyElement]]; } - [self addBody:body]; + [correctionMessage addBody:body]; [correctionMessage addMessageCorrectionWithID:[self elementID]]; } diff --git a/Extensions/XEP-0313/XMPPMessage+XEP_0313.h b/Extensions/XEP-0313/XMPPMessage+XEP_0313.h new file mode 100644 index 0000000000..7efa68a5c0 --- /dev/null +++ b/Extensions/XEP-0313/XMPPMessage+XEP_0313.h @@ -0,0 +1,16 @@ +// +// XMPPMessage+XEP_0313.h +// XMPPFramework +// +// Created by Chris Ballinger on 10/23/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPMessage.h" + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPMessage (XEP_0313) +/** XEP-0313: MAM element */ +@property (nonatomic, nullable, readonly) NSXMLElement *mamResult; +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0313/XMPPMessage+XEP_0313.m b/Extensions/XEP-0313/XMPPMessage+XEP_0313.m new file mode 100644 index 0000000000..abb8c4e803 --- /dev/null +++ b/Extensions/XEP-0313/XMPPMessage+XEP_0313.m @@ -0,0 +1,20 @@ +// +// XMPPMessage+XEP_0313.m +// XMPPFramework +// +// Created by Chris Ballinger on 10/23/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPMessage+XEP_0313.h" +#import "XMPPMessageArchiveManagement.h" +#import "NSXMLElement+XMPP.h" + +@implementation XMPPMessage (XEP_0313) + +- (NSXMLElement*) mamResult { + NSXMLElement *result = [self elementForName:@"result" xmlns:XMLNS_XMPP_MAM]; + return result; +} + +@end diff --git a/Extensions/XEP-0313/XMPPMessageArchiveManagement.h b/Extensions/XEP-0313/XMPPMessageArchiveManagement.h new file mode 100644 index 0000000000..2dcd1b0d07 --- /dev/null +++ b/Extensions/XEP-0313/XMPPMessageArchiveManagement.h @@ -0,0 +1,56 @@ +// +// XMPPMessageArchiveManagement.h +// +// Created by Andres Canal on 4/8/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPModule.h" +#import "XMPPResultSet.h" +#import "XMPPIQ.h" +@import KissXML; + +@class XMPPIDTracker; +@class XMPPMessage; + + +NS_ASSUME_NONNULL_BEGIN + +/** 'urn:xmpp:mam:2' */ +extern NSString *const XMLNS_XMPP_MAM; + +@interface XMPPMessageArchiveManagement : XMPPModule + +/** + When this is set to 0 (the default), the module will finish retrieving messages after receiving the first page IQ result. + Setting it to a non-zero value will cause the module to automatically repeat the query for further pages of specified size until a "fin" result with "complete=true" attribute is received. + Use NSNotFound to indicate that there is no client-side page size preference. + */ +@property (readwrite, assign) NSInteger resultAutomaticPagingPageSize; + +- (void)retrieveMessageArchiveWithFields:(nullable NSArray *)fields + withResultSet:(nullable XMPPResultSet *)resultSet; + +- (void)retrieveMessageArchiveAt:(nullable XMPPJID *)archiveJID + withFields:(nullable NSArray *)fields + withResultSet:(nullable XMPPResultSet *)resultSet; + +- (void)retrieveFormFields; + ++ (NSXMLElement *)fieldWithVar:(NSString *)var + type:(nullable NSString *)type + andValue:(NSString *)value; + +@end + +@protocol XMPPMessageArchiveManagementDelegate +@optional +- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didFinishReceivingMessagesWithSet:(XMPPResultSet *)resultSet; +- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didReceiveMAMMessage:(XMPPMessage *)message; +- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didFailToReceiveMessages:(nullable XMPPIQ *)error; + +- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didReceiveFormFields:(XMPPIQ *)iq; +- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didFailToReceiveFormFields:(XMPPIQ *)iq; +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0313/XMPPMessageArchiveManagement.m b/Extensions/XEP-0313/XMPPMessageArchiveManagement.m new file mode 100644 index 0000000000..fcf5bf12c5 --- /dev/null +++ b/Extensions/XEP-0313/XMPPMessageArchiveManagement.m @@ -0,0 +1,225 @@ +// +// XMPPMessageArchiveManagement.m +// +// Created by Andres Canal on 4/8/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPMessageArchiveManagement.h" +#import "XMPPFramework.h" +#import "XMPPLogging.h" +#import "XMPPIDTracker.h" +#import "NSXMLElement+XEP_0297.h" +#import "XMPPLogging.h" +#import "XMPPMessage+XEP_0313.h" + +// Log levels: off, error, warn, info, verbose +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_SEND_RECV; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +NSString *const XMLNS_XMPP_MAM = @"urn:xmpp:mam:2"; +static NSString *const QueryIdAttributeName = @"queryid"; + + +@interface XMPPMessageArchiveManagement() +/** Only safe to access on moduleQueue. */ +@property (nonatomic, strong, readonly, nonnull) NSMutableSet *outstandingQueryIds; +/** Setup in activate: */ +@property (strong, nonatomic, nullable, readonly) XMPPIDTracker *xmppIDTracker; +@end + +@implementation XMPPMessageArchiveManagement +@synthesize resultAutomaticPagingPageSize = _resultAutomaticPagingPageSize; +@synthesize xmppIDTracker = _xmppIDTracker; + +- (instancetype) initWithDispatchQueue:(dispatch_queue_t)queue { + if (self = [super initWithDispatchQueue:queue]) { + _outstandingQueryIds = [NSMutableSet set]; + } + return self; +} + +- (NSInteger)resultAutomaticPagingPageSize +{ + __block NSInteger result = NO; + [self performBlock:^{ + result = self->_resultAutomaticPagingPageSize; + }]; + return result; +} + +- (void)setResultAutomaticPagingPageSize:(NSInteger)resultAutomaticPagingPageSize +{ + [self performBlockAsync:^{ + self->_resultAutomaticPagingPageSize = resultAutomaticPagingPageSize; + }]; +} + +- (void)retrieveMessageArchiveWithFields:(NSArray *)fields withResultSet:(XMPPResultSet *)resultSet { + [self retrieveMessageArchiveAt:nil withFields:fields withResultSet:resultSet]; +} + +- (void)retrieveMessageArchiveAt:(XMPPJID *)archiveJID withFields:(NSArray *)fields withResultSet:(XMPPResultSet *)resultSet { + NSXMLElement *formElement = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"]; + [formElement addAttributeWithName:@"type" stringValue:@"submit"]; + [formElement addChild:[XMPPMessageArchiveManagement fieldWithVar:@"FORM_TYPE" type:@"hidden" andValue:XMLNS_XMPP_MAM]]; + + for (NSXMLElement *field in fields) { + [formElement addChild:field]; + } + + [self retrieveMessageArchiveAt:archiveJID withFormElement:formElement resultSet:resultSet]; +} + +- (void)retrieveMessageArchiveAt:(XMPPJID *)archiveJID withFormElement:(NSXMLElement *)formElement resultSet:(XMPPResultSet *)resultSet { + [self performBlockAsync:^{ + XMPPIQ *iq = [XMPPIQ iqWithType:@"set"]; + [iq addAttributeWithName:@"id" stringValue:[XMPPStream generateUUID]]; + + if (archiveJID) { + [iq addAttributeWithName:@"to" stringValue:[archiveJID full]]; + } + + NSString *queryId = [XMPPStream generateUUID]; + [self->_outstandingQueryIds addObject:queryId]; + + NSXMLElement *queryElement = [NSXMLElement elementWithName:@"query" xmlns:XMLNS_XMPP_MAM]; + [queryElement addAttributeWithName:QueryIdAttributeName stringValue:queryId]; + [iq addChild:queryElement]; + + [queryElement addChild:formElement]; + + if (resultSet) { + [queryElement addChild:resultSet]; + } + + [self.xmppIDTracker addElement:iq + target:self + selector:@selector(handleMessageArchiveIQ:withInfo:) + timeout:60]; + + [self->xmppStream sendElement:iq]; + }]; +} + +- (void)handleMessageArchiveIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)trackerInfo { + + if ([[iq type] isEqualToString:@"result"]) { + + NSXMLElement *finElement = [iq elementForName:@"fin" xmlns:XMLNS_XMPP_MAM]; + NSString *queryId = [finElement attributeStringValueForName:QueryIdAttributeName]; + NSXMLElement *setElement = [finElement elementForName:@"set" xmlns:@"http://jabber.org/protocol/rsm"]; + + XMPPResultSet *resultSet = [XMPPResultSet resultSetFromElement:setElement]; + NSString *lastId = [resultSet elementForName:@"last"].stringValue; + + if (self.resultAutomaticPagingPageSize == 0 || [finElement attributeBoolValueForName:@"complete"] || !lastId) { + + if (queryId.length) { + [self.outstandingQueryIds removeObject:queryId]; + } + + [multicastDelegate xmppMessageArchiveManagement:self didFinishReceivingMessagesWithSet:resultSet]; + return; + } + + XMPPIQ *originalIq = [XMPPIQ iqFromElement:[trackerInfo element]]; + XMPPJID *originalArchiveJID = [originalIq to]; + NSXMLElement *originalFormElement = [[[originalIq elementForName:@"query"] elementForName:@"x"] copy]; + XMPPResultSet *pagingResultSet = [[XMPPResultSet alloc] initWithMax:self.resultAutomaticPagingPageSize after:lastId]; + + [self retrieveMessageArchiveAt:originalArchiveJID withFormElement:originalFormElement resultSet:pagingResultSet]; + } else { + [multicastDelegate xmppMessageArchiveManagement:self didFailToReceiveMessages:iq]; + } +} + ++ (NSXMLElement *)fieldWithVar:(NSString *)var type:(NSString *)type andValue:(NSString *)value { + NSXMLElement *field = [NSXMLElement elementWithName:@"field"]; + [field addAttributeWithName:@"var" stringValue:var]; + + if(type){ + [field addAttributeWithName:@"type" stringValue:type]; + } + + NSXMLElement *elementValue = [NSXMLElement elementWithName:@"value"]; + elementValue.stringValue = value; + + [field addChild:elementValue]; + + return field; +} + +- (void)retrieveFormFields { + [self performBlockAsync:^{ + XMPPIQ *iq = [XMPPIQ iqWithType:@"get"]; + [iq addAttributeWithName:@"id" stringValue:[XMPPStream generateUUID]]; + + NSXMLElement *queryElement = [NSXMLElement elementWithName:@"query" xmlns:XMLNS_XMPP_MAM]; + [iq addChild:queryElement]; + + [self.xmppIDTracker addElement:iq + target:self + selector:@selector(handleFormFieldsIQ:withInfo:) + timeout:60]; + + [self->xmppStream sendElement:iq]; + }]; +} + +- (void)handleFormFieldsIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)trackerInfo { + + if ([[iq type] isEqualToString:@"result"]) { + [multicastDelegate xmppMessageArchiveManagement:self didReceiveFormFields:iq]; + } else { + [multicastDelegate xmppMessageArchiveManagement:self didFailToReceiveFormFields:iq]; + } +} + +- (BOOL)activate:(XMPPStream *)aXmppStream { + if ([super activate:aXmppStream]) { + _xmppIDTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue]; + return YES; + } + return NO; +} + +- (void)deactivate { + [self performBlock:^{ @autoreleasepool { + [self.xmppIDTracker removeAllIDs]; + self->_xmppIDTracker = nil; + }}]; + [super deactivate]; +} + +#pragma mark XMPPStream Delegate + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq +{ + NSString *type = [iq type]; + if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]) + { + return [self.xmppIDTracker invokeForID:[iq elementID] withObject:iq]; + } + + return NO; +} + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message { + NSXMLElement *result = message.mamResult; + BOOL forwarded = result.hasForwardedStanza; + if (!forwarded) { + return; + } + NSString *queryID = [result attributeForName:QueryIdAttributeName].stringValue; + if (queryID.length && [self.outstandingQueryIds containsObject:queryID]) { + [multicastDelegate xmppMessageArchiveManagement:self didReceiveMAMMessage:message]; + } else { + XMPPLogWarn(@"Received unexpected MAM response queryid %@", queryID); + } +} + +@end diff --git a/Extensions/XEP-0313/XMPPRoomLightCoreDataStorage+XEP_0313.h b/Extensions/XEP-0313/XMPPRoomLightCoreDataStorage+XEP_0313.h new file mode 100644 index 0000000000..9daf2676b1 --- /dev/null +++ b/Extensions/XEP-0313/XMPPRoomLightCoreDataStorage+XEP_0313.h @@ -0,0 +1,17 @@ +#import "XMPPRoomLightCoreDataStorage.h" + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPRoomLightCoreDataStorage (XEP_0313) + +/** + A helper method to use when synchronizing with a remote message store. + - Infers whether the message is incoming or outgoing + - Will not store a message it considers a duplicate of another message stored earlier + */ +- (void)importRemoteArchiveMessage:(XMPPMessage *)message + withTimestamp:(nullable NSDate *)archiveTimestamp + inRoom:(XMPPRoomLight *)room + fromStream:(XMPPStream *)stream; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0313/XMPPRoomLightCoreDataStorage+XEP_0313.m b/Extensions/XEP-0313/XMPPRoomLightCoreDataStorage+XEP_0313.m new file mode 100644 index 0000000000..1354076996 --- /dev/null +++ b/Extensions/XEP-0313/XMPPRoomLightCoreDataStorage+XEP_0313.m @@ -0,0 +1,83 @@ +#import "XMPPRoomLightCoreDataStorage+XEP_0313.h" +#import "XMPPRoomLightCoreDataStorageProtected.h" +#import "XMPPRoomMessage.h" + +@interface XMPPMessage (XMPPRoomLightCoreDataStorage_XEP_0313) + +- (NSDictionary *)xElementStringsDictionary; + +@end + +@implementation XMPPRoomLightCoreDataStorage (XEP_0313) + +- (void)importRemoteArchiveMessage:(XMPPMessage *)message withTimestamp:(NSDate *)archiveTimestamp inRoom:(XMPPRoomLight *)room fromStream:(XMPPStream *)stream +{ + XMPPJID *sender = [XMPPJID jidWithString:[message from].resource]; + XMPPJID *me = [self myJIDForXMPPStream:stream]; + BOOL isOutgoing = [sender isEqualToJID:me options:XMPPJIDCompareBare]; + + [self scheduleBlock:^{ + if ([self isMessageUnique:message withRemoteTimestamp:archiveTimestamp inRoom:room]) { + [self insertMessage:message outgoing:isOutgoing remoteTimestamp:archiveTimestamp forRoom:room stream:stream]; + } + }]; +} + +- (BOOL)isMessageUnique:(XMPPMessage *)message withRemoteTimestamp:(NSDate *)remoteTimestamp inRoom:(XMPPRoomLight *)room +{ + NSManagedObjectContext *moc = [self managedObjectContext]; + NSEntityDescription *messageEntity = [self messageEntity:moc]; + + NSString *messageBody = [[message elementForName:@"body"] stringValue]; + + NSString *senderFullJID = [[message from] full]; + + NSDate *minLocalTimestamp = [remoteTimestamp dateByAddingTimeInterval:-60]; + NSDate *maxLocalTimestamp = [remoteTimestamp dateByAddingTimeInterval: 60]; + + NSPredicate *contentPredicate = [NSPredicate predicateWithFormat:@"body = %@", messageBody]; + NSPredicate *locationPredicate = [NSPredicate predicateWithFormat:@"jidStr = %@", senderFullJID]; + NSPredicate *preciseTimestampPredicate = [NSPredicate predicateWithFormat:@"remoteTimestamp = %@", remoteTimestamp]; + NSPredicate *approximateTimestampPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[[NSPredicate predicateWithFormat:@"remoteTimestamp = nil"], + [NSPredicate predicateWithFormat:@"localTimestamp BETWEEN {%@, %@}", minLocalTimestamp, maxLocalTimestamp]]]; + NSPredicate *timestampPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[preciseTimestampPredicate, approximateTimestampPredicate]]; + NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[contentPredicate, locationPredicate, timestampPredicate]]; + + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; + fetchRequest.entity = messageEntity; + fetchRequest.predicate = predicate; + fetchRequest.fetchLimit = 1; + + NSArray *results = [moc executeFetchRequest:fetchRequest error:NULL]; + + if (messageBody.length == 0) { + // for non-body messages fall back to comparing elements; this has to be done post-fetch as managed objects lack relevant attributes + results = [results filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + id evaluatedMessage = evaluatedObject; + return [[[evaluatedMessage message] xElementStringsDictionary] isEqualToDictionary:[message xElementStringsDictionary]]; + }]]; + } + + return results && results.count == 0; +} + +@end + +@implementation XMPPMessage (XMPPRoomLightCoreDataStorage_XEP_0313) + +- (NSDictionary *)xElementStringsDictionary +{ + NSMutableDictionary *xElementStringsDictionary = [NSMutableDictionary dictionary]; + + // Enumerating children due to a bug in Apple's NSXML implementation where -elementsForName: does not pick up namespace-qualified ones + for (NSXMLNode *node in self.children) { + if (node.kind == NSXMLElementKind && [node.name isEqualToString:@"x"]) { + // Returning XML strings as equality test in KissXML is based on comparing node pointers + xElementStringsDictionary[node.name] = node.XMLString; + } + } + + return xElementStringsDictionary; +} + +@end diff --git a/Extensions/XEP-0333/XMPPMessage+XEP_0333.h b/Extensions/XEP-0333/XMPPMessage+XEP_0333.h index e7e70e66b9..e7bc0bc300 100644 --- a/Extensions/XEP-0333/XMPPMessage+XEP_0333.h +++ b/Extensions/XEP-0333/XMPPMessage+XEP_0333.h @@ -1,16 +1,17 @@ #import "XMPPMessage.h" +NS_ASSUME_NONNULL_BEGIN @interface XMPPMessage (XEP_0333) -- (BOOL)hasChatMarker; +@property (nonatomic, readonly) BOOL hasChatMarker; -- (BOOL)hasMarkableChatMarker; -- (BOOL)hasReceivedChatMarker; -- (BOOL)hasDisplayedChatMarker; -- (BOOL)hasAcknowledgedChatMarker; +@property (nonatomic, readonly) BOOL hasMarkableChatMarker; +@property (nonatomic, readonly) BOOL hasReceivedChatMarker; +@property (nonatomic, readonly) BOOL hasDisplayedChatMarker; +@property (nonatomic, readonly) BOOL hasAcknowledgedChatMarker; -- (NSString *)chatMarker; -- (NSString *)chatMarkerID; +@property (nonatomic, readonly, nullable) NSString *chatMarker; +@property (nonatomic, readonly, nullable) NSString *chatMarkerID; - (void)addMarkableChatMarker; - (void)addReceivedChatMarkerWithID:(NSString *)elementID; @@ -26,3 +27,4 @@ - (XMPPMessage *)generateAcknowledgedChatMarkerIncludingThread:(BOOL)includingThread; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0334/XMPPMessage+XEP_0334.h b/Extensions/XEP-0334/XMPPMessage+XEP_0334.h new file mode 100644 index 0000000000..40814f41c7 --- /dev/null +++ b/Extensions/XEP-0334/XMPPMessage+XEP_0334.h @@ -0,0 +1,54 @@ +// +// XMPPMessage+XEP_0334.h +// Pods +// +// Created by Chris Ballinger on 4/16/16. +// +// + +#import "XMPPMessage.h" + +typedef NS_ENUM(NSInteger, XMPPMessageStorage) { + /** + Unknown storage hint. Attempting to add this is a no-op. + */ + XMPPMessageStorageUnknown, + /** + The hint informs entities that they shouldn't store the message in any permanent or semi-permanent public or private archive (such as described in Message Archiving (XEP-0136) [5] and Message Archive Management (XEP-0313) [6]) or in logs (such as chatroom logs). + */ + XMPPMessageStorageNoPermanentStore, + /** + A message containing a hint should not be stored by a server either permanently (as above) or temporarily, e.g. for later delivery to an offline client, or to users not currently present in a chatroom. + */ + XMPPMessageStorageNoStore, + /** + Messages with the hint should not be copied to addresses other than the one to which it is addressed, for example through Message Carbons (XEP-0280) [7]. + + This hint MUST only be included on messages addressed to full JIDs and explicitly does not override the behaviour defined in XMPP IM [8] for handling messages to bare JIDs, which may involve copying to multiple resources, or multiple occupants in a Multi-User Chat (XEP-0045) [9] room. + */ + XMPPMessageStorageNoCopy, + /** + A message containing the hint that is not of type 'error' SHOULD be stored by the entity. + */ + XMPPMessageStorageStore +}; + +/** + XEP-0334: Message Processing Hints + http://xmpp.org/extensions/xep-0334.html + + This specification aims to solve the following common problems, and allow a sender to hint to the recipient: + + - Whether to store a message (e.g. for archival or as an 'offline message'). + - Whether to copy a message to other resources. + - Whether to store a message that would not have been stored under normal conditions + */ +@interface XMPPMessage (XEP_0334) + +/** add a storage hint to message element */ +-(void) addStorageHint:(XMPPMessageStorage)storageHint; + +/** Contains array of boxed XMPPMessageStorage values present in the message element. Empty array if none found. */ +- (nonnull NSArray*)storageHints; + +@end diff --git a/Extensions/XEP-0334/XMPPMessage+XEP_0334.m b/Extensions/XEP-0334/XMPPMessage+XEP_0334.m new file mode 100644 index 0000000000..cbf3f88154 --- /dev/null +++ b/Extensions/XEP-0334/XMPPMessage+XEP_0334.m @@ -0,0 +1,74 @@ +// +// XMPPMessage+XEP_0334.m +// Pods +// +// Created by Chris Ballinger on 4/16/16. +// +// + +#import "XMPPMessage+XEP_0334.h" +#import "NSXMLElement+XMPP.h" + +#define XMLNS_STORAGE_HINTS @"urn:xmpp:hints" + +static NSString * const kMessageStore = @"store"; +static NSString * const kMessageNoCopy = @"no-copy"; +static NSString * const kMessageNoStore = @"no-store"; +static NSString * const kMessageNoPermanentStore = @"no-permanent-store"; + +@implementation XMPPMessage (XEP_0334) + +-(void) addStorageHint:(XMPPMessageStorage)storageHint { + NSString *storageName = [self nameForStorageHint:storageHint]; + if (!storageName.length) { + return; + } + NSXMLElement *storageElement = [NSXMLElement elementWithName:storageName xmlns:XMLNS_STORAGE_HINTS]; + [self addChild:storageElement]; +} + +- (NSString*) nameForStorageHint:(XMPPMessageStorage)storageHint { + NSString *storage = nil; + switch (storageHint) { + case XMPPMessageStorageStore: + storage = kMessageStore; + break; + case XMPPMessageStorageNoCopy: + storage = kMessageNoCopy; + break; + case XMPPMessageStorageNoStore: + storage = kMessageNoStore; + break; + case XMPPMessageStorageNoPermanentStore: + storage = kMessageNoPermanentStore; + break; + default: + storage = @""; + break; + } + return storage; +} + +- (nonnull NSArray*)storageHints { + NSArray *elements = [self elementsForXmlns:XMLNS_STORAGE_HINTS]; + NSMutableArray *boxedHints = [NSMutableArray arrayWithCapacity:elements.count]; + [elements enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + XMPPMessageStorage storageHint = XMPPMessageStorageUnknown; + NSString *storageName = [obj name]; + if ([storageName isEqualToString:kMessageStore]) { + storageHint = XMPPMessageStorageStore; + } else if ([storageName isEqualToString:kMessageNoCopy]) { + storageHint = XMPPMessageStorageNoCopy; + } else if ([storageName isEqualToString:kMessageNoStore]) { + storageHint = XMPPMessageStorageNoStore; + } else if ([storageName isEqualToString:kMessageNoPermanentStore]) { + storageHint = XMPPMessageStorageNoPermanentStore; + } + [boxedHints addObject:@(storageHint)]; + }]; + return boxedHints; +} + + + +@end diff --git a/Extensions/XEP-0335/NSXMLElement+XEP_0335.h b/Extensions/XEP-0335/NSXMLElement+XEP_0335.h index 334e511a68..ebc9e7d842 100644 --- a/Extensions/XEP-0335/NSXMLElement+XEP_0335.h +++ b/Extensions/XEP-0335/NSXMLElement+XEP_0335.h @@ -1,21 +1,21 @@ #import -#if TARGET_OS_IPHONE -#import "DDXML.h" -#endif +@import KissXML; +NS_ASSUME_NONNULL_BEGIN @interface NSXMLElement (XEP_0335) -- (NSXMLElement *)JSONContainer; +@property (nonatomic, readonly) NSXMLElement *JSONContainer; -- (BOOL)isJSONContainer; -- (BOOL)hasJSONContainer; +@property (nonatomic, readonly) BOOL isJSONContainer; +@property (nonatomic, readonly) BOOL hasJSONContainer; -- (NSString *)JSONContainerString; -- (NSData *)JSONContainerData; -- (id)JSONContainerObject; +@property (nonatomic, readonly) NSString *JSONContainerString; +@property (nonatomic, readonly) NSData *JSONContainerData; +@property (nonatomic, readonly, nullable) id JSONContainerObject; - (void)addJSONContainerWithString:(NSString *)JSONContainerString; - (void)addJSONContainerWithData:(NSData *)JSONContainerData; - (void)addJSONContainerWithObject:(id)JSONContainerObject; @end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0352/NSXMLElement+XEP_0352.h b/Extensions/XEP-0352/NSXMLElement+XEP_0352.h new file mode 100644 index 0000000000..7508909d16 --- /dev/null +++ b/Extensions/XEP-0352/NSXMLElement+XEP_0352.h @@ -0,0 +1,11 @@ + +#import "NSXMLElement+XMPP.h" + +NS_ASSUME_NONNULL_BEGIN +@interface NSXMLElement (XEP0352) + ++ (instancetype)indicateInactiveElement; ++ (instancetype)indicateActiveElement; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0352/NSXMLElement+XEP_0352.m b/Extensions/XEP-0352/NSXMLElement+XEP_0352.m new file mode 100644 index 0000000000..1e02012e0e --- /dev/null +++ b/Extensions/XEP-0352/NSXMLElement+XEP_0352.m @@ -0,0 +1,18 @@ + +#import "NSXMLElement+XEP_0352.h" + +#define XMLNS_XMPP_CLIENT_STATE_INDICATION @"urn:xmpp:csi:0" + +@implementation NSXMLElement (XEP0352) + ++ (instancetype)indicateActiveElement +{ + return [NSXMLElement elementWithName:@"active" xmlns:XMLNS_XMPP_CLIENT_STATE_INDICATION]; +} + ++ (instancetype)indicateInactiveElement +{ + return [NSXMLElement elementWithName:@"inactive" xmlns:XMLNS_XMPP_CLIENT_STATE_INDICATION]; +} + +@end diff --git a/Extensions/XEP-0357/XMPPIQ+XEP_0357.h b/Extensions/XEP-0357/XMPPIQ+XEP_0357.h new file mode 100644 index 0000000000..6087c5522a --- /dev/null +++ b/Extensions/XEP-0357/XMPPIQ+XEP_0357.h @@ -0,0 +1,59 @@ +// +// NSXMLElement+NSXMLElement_XEP_0357.h +// +// Created by David Chiles on 2/9/16. +// +// + +#import "XMPPIQ.h" +@class XMPPJID; + +/** + XMPPIQ (XEP0357) is a class extension on XMPPIQ for creating the elements for XEP-0357 http://xmpp.org/extensions/xep-0357.html + */ +extern NSString * __nonnull const XMPPPushXMLNS; + +@interface XMPPIQ (XEP0357) + +/** + Creates an IQ stanza for enabling push notificiations. http://xmpp.org/extensions/xep-0357.html#enabling + + @param jid The jid of the XMPP Push Service + @param node Optional node of the XMPP Push Service + @param options Optional values to passed to your XMPP service (this is likely some sort of secret or token to validate this user/device with teh app server) + @return An IQ stanza + */ ++ (nonnull instancetype)enableNotificationsElementWithJID:(nonnull XMPPJID *)jid node:(nullable NSString *)node options:(nullable NSDictionary *)options; + +/** + Creates an IQ stanza for enabling push notificiations. http://xmpp.org/extensions/xep-0357.html#enabling + + @param jid The jid of the XMPP Push Service + @param node Optional node of the XMPP Push Service + @param options Optional values to passed to your XMPP service (this is likely some sort of secret or token to validate this user/device with teh app server) + @param elementId the XMPPElement id for tracking responses + @return An IQ stanza + */ ++ (nonnull instancetype)enableNotificationsElementWithJID:(nonnull XMPPJID *)jid node:(nullable NSString *)node options:(nullable NSDictionary *)options elementId:(nullable NSString*)elementId; + +/** + Creates an IQ stanza for disable push notifications. http://xmpp.org/extensions/xep-0357.html#disabling + + @param jid the jid of the XMPP Push Service + @param node the node of the XMPP push Service + @return an IQ Stanza + */ ++ (nonnull instancetype)disableNotificationsElementWithJID:(nonnull XMPPJID *)jid node:(nullable NSString *)node +; +/** + Creates an IQ stanza for disable push notifications. http://xmpp.org/extensions/xep-0357.html#disabling + + @param jid the jid of the XMPP Push Service + @param node the node of the XMPP push Service + @param elementId the XMPPElement id for tracking responses + @return an IQ Stanza + */ ++ (nonnull instancetype)disableNotificationsElementWithJID:(nonnull XMPPJID *)jid node:(nullable NSString *)node elementId:(nullable NSString*)elementId; + + +@end diff --git a/Extensions/XEP-0357/XMPPIQ+XEP_0357.m b/Extensions/XEP-0357/XMPPIQ+XEP_0357.m new file mode 100644 index 0000000000..603ea1a62b --- /dev/null +++ b/Extensions/XEP-0357/XMPPIQ+XEP_0357.m @@ -0,0 +1,71 @@ +// +// NSXMLElement+NSXMLElement_XEP_0357.m +// Pods +// +// Created by David Chiles on 2/9/16. +// +// + +#import "XMPPIQ+XEP_0357.h" +#import "XMPPJID.h" +#import "NSXMLElement+XMPP.h" +#import "XMPPStream.h" + +NSString *const XMPPPushXMLNS = @"urn:xmpp:push:0"; + +@implementation XMPPIQ (XEP0357) + ++ (instancetype)enableNotificationsElementWithJID:(XMPPJID *)jid node:(NSString *)node options:(nullable NSDictionary *)options { + return [self enableNotificationsElementWithJID:jid node:node options:options elementId:nil]; +} + ++ (instancetype)enableNotificationsElementWithJID:(XMPPJID *)jid node:(NSString *)node options:(nullable NSDictionary *)options elementId:(NSString *)elementId +{ + if (!elementId) { + elementId = [XMPPStream generateUUID]; + } + NSXMLElement *enableElement = [self elementWithName:@"enable" xmlns:XMPPPushXMLNS]; + [enableElement addAttributeWithName:@"jid" stringValue:[jid full]]; + if ([node length]) { + [enableElement addAttributeWithName:@"node" stringValue:node]; + } + + if ([options count]) { + NSXMLElement *dataForm = [self elementWithName:@"x" xmlns:@"jabber:x:data"]; + [dataForm addAttributeWithName:@"type" stringValue:@"submit"]; + NSXMLElement *formTypeField = [NSXMLElement elementWithName:@"field"]; + [formTypeField addAttributeWithName:@"var" stringValue:@"FORM_TYPE"]; + [formTypeField addChild:[NSXMLElement elementWithName:@"value" stringValue:@"http://jabber.org/protocol/pubsub#publish-options"]]; + [dataForm addChild:formTypeField]; + + [options enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { + NSXMLElement *formField = [NSXMLElement elementWithName:@"field"]; + [formField addAttributeWithName:@"var" stringValue:key]; + [formField addChild:[NSXMLElement elementWithName:@"value" stringValue:obj]]; + [dataForm addChild:formField]; + }]; + [enableElement addChild:dataForm]; + } + + return [self iqWithType:@"set" elementID:elementId child:enableElement]; + +} + ++ (instancetype)disableNotificationsElementWithJID:(XMPPJID *)jid node:(NSString *)node { + return [self disableNotificationsElementWithJID:jid node:node elementId:nil]; +} + ++ (instancetype)disableNotificationsElementWithJID:(XMPPJID *)jid node:(NSString *)node elementId:(nullable NSString *)elementId +{ + if (!elementId) { + elementId = [XMPPStream generateUUID]; + } + NSXMLElement *disableElement = [self elementWithName:@"disable" xmlns:XMPPPushXMLNS]; + [disableElement addAttributeWithName:@"jid" stringValue:[jid full]]; + if ([node length]) { + [disableElement addAttributeWithName:@"node" stringValue:node]; + } + return [self iqWithType:@"set" elementID:elementId child:disableElement]; +} + +@end diff --git a/Extensions/XEP-0357/XMPPPushModule.h b/Extensions/XEP-0357/XMPPPushModule.h new file mode 100644 index 0000000000..5163be4c35 --- /dev/null +++ b/Extensions/XEP-0357/XMPPPushModule.h @@ -0,0 +1,89 @@ +// +// XMPPPushModule.h +// ChatSecure +// +// Created by Chris Ballinger on 2/27/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPModule.h" +#import "XMPPJID.h" +#import "XMPPIQ.h" + +@import KissXML; + +@class XMPPPushOptions; + +typedef NS_ENUM(NSUInteger, XMPPPushStatus) { + XMPPPushStatusUnknown, + XMPPPushStatusNotRegistered, + XMPPPushStatusRegistering, + XMPPPushStatusRegistered, + XMPPPushStatusError +}; + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPPushModule : XMPPModule + +/** + * This value only reflects local in-memory status and will not check the server. It is reset to XMPPPushStatusUnknown after + * re-authentication because some servers clear this value on new streams. + */ +- (XMPPPushStatus) registrationStatusForServerJID:(XMPPJID*)serverJID; + +/** Manually update your push registration. */ +- (void) registerForPushWithOptions:(XMPPPushOptions*)options + elementId:(nullable NSString*)elementId; + +/** Disables push for a specified node on serverJID. Warning: If node is nil it will disable for all nodes (and disable push on your other devices) */ +- (void) disablePushForServerJID:(XMPPJID*)serverJID + node:(nullable NSString*)node + elementId:(nullable NSString*)elementId; + +/** This will trigger the same logic as xmppStreamDidAuthenticate: */ +- (void) refresh; + +@end + +/** Multicast delegate methods */ +@protocol XMPPPushDelegate +@optional + +/** This is called after capabilities are processed so you can safely call registerForPushWithOptions or disablePush */ +- (void)pushModule:(XMPPPushModule*)module readyWithCapabilities:(NSXMLElement *)caps jid:(XMPPJID *)jid; + +- (void)pushModule:(XMPPPushModule*)module +didRegisterWithResponseIq:(XMPPIQ*)responseIq + outgoingIq:(XMPPIQ*)outgoingIq; + +- (void)pushModule:(XMPPPushModule*)module +failedToRegisterWithErrorIq:(nullable XMPPIQ*)errorIq + outgoingIq:(XMPPIQ*)outgoingIq; + +- (void)pushModule:(XMPPPushModule*)module +disabledPushForServerJID:(XMPPJID*)serverJID + node:(nullable NSString*)node + responseIq:(XMPPIQ*)responseIq + outgoingIq:(XMPPIQ*)outgoingIq; + +- (void)pushModule:(XMPPPushModule*)module +failedToDisablePushWithErrorIq:(nullable XMPPIQ*)errorIq + serverJID:(XMPPJID*)serverJID + node:(nullable NSString*)node + outgoingIq:(XMPPIQ*)outgoingIq; + +@end + +@interface XMPPPushOptions : NSObject +/** Maps to FORM_OPTIONS. This could include the device token, secret value, etc. */ +@property (nonatomic, strong, readonly) NSDictionary *formOptions; +/** This should be a unique, persistent value for this app install. */ +@property (nonatomic, strong, readonly) NSString *node; +/** The server JID that's running the pubsub node */ +@property (nonatomic, strong, readonly) XMPPJID *serverJID; + +- (instancetype) initWithServerJID:(XMPPJID*)serverJID + node:(NSString*)node + formOptions:(NSDictionary*)formOptions; +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0357/XMPPPushModule.m b/Extensions/XEP-0357/XMPPPushModule.m new file mode 100644 index 0000000000..f3df4e0617 --- /dev/null +++ b/Extensions/XEP-0357/XMPPPushModule.m @@ -0,0 +1,286 @@ +// +// XMPPPushModule.m +// ChatSecure +// +// Created by Chris Ballinger on 2/27/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPPushModule.h" +#import "XMPPLogging.h" +#import "XMPPIDTracker.h" +#import "XMPPCapabilities.h" +#import "XMPPIQ+XEP_0357.h" +#import "XMPPInternal.h" + +// Log levels: off, error, warn, info, verbose +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_SEND_RECV; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +@interface XMPPPushModule() +@property (nonatomic, strong, readonly) XMPPIDTracker *tracker; +/** Only access this from within the moduleQueue */ +@property (nonatomic, strong, readonly) NSMutableSet *capabilitiesModules; +/** Prevents multiple requests. Only access this from within the moduleQueue */ +@property (nonatomic, strong, readonly) NSMutableDictionary *registrationStatus; +@end + +@implementation XMPPPushModule + +#pragma mark Public API + +/** + * This value only reflects local in-memory status and will not check the server. It is reset to XMPPPushStatusUnknown after + * re-authentication because some servers clear this value on new streams. + */ +- (XMPPPushStatus) registrationStatusForServerJID:(XMPPJID*)serverJID { + NSParameterAssert(serverJID != nil); + if (!serverJID) { return XMPPPushStatusUnknown; } + __block XMPPPushStatus status = XMPPPushStatusUnknown; + [self performBlock:^{ + NSNumber *number = [self.registrationStatus objectForKey:serverJID]; + status = number.unsignedIntegerValue; + }]; + return status; +} + +- (void) setRegistrationStatus:(XMPPPushStatus)registrationStatus forServerJID:(XMPPJID*)serverJID { + NSParameterAssert(serverJID != nil); + if (!serverJID) { return; } + [self performBlockAsync:^{ + [self.registrationStatus setObject:@(registrationStatus) forKey:serverJID]; + }]; +} + +/** Manually refresh your push registration */ +- (void) registerForPushWithOptions:(XMPPPushOptions*)options + elementId:(nullable NSString*)elementId { + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self performBlockAsync:^{ + if ([self registrationStatusForServerJID:options.serverJID] == XMPPPushStatusRegistering) { + XMPPLogVerbose(@"Already registering push options..."); + return; + } + NSString *eid = [self fixElementId:elementId]; + XMPPIQ *enableElement = [XMPPIQ enableNotificationsElementWithJID:options.serverJID node:options.node options:options.formOptions elementId:eid]; + [self.tracker addElement:enableElement block:^(XMPPIQ *responseIq, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + if (!responseIq || [responseIq isErrorIQ]) { + // timeout + XMPPLogWarn(@"refreshRegistration error: %@ %@", enableElement, responseIq); + [strongSelf setRegistrationStatus:XMPPPushStatusError forServerJID:options.serverJID]; + [weakMulticast pushModule:strongSelf failedToRegisterWithErrorIq:responseIq outgoingIq:enableElement]; + return; + } + [strongSelf setRegistrationStatus:XMPPPushStatusRegistered forServerJID:options.serverJID]; + [weakMulticast pushModule:strongSelf didRegisterWithResponseIq:responseIq outgoingIq:enableElement]; + } timeout:30]; + [self setRegistrationStatus:XMPPPushStatusRegistering forServerJID:options.serverJID]; + [self->xmppStream sendElement:enableElement]; + }]; +} + +/** Disables push for a specified node on serverJID. Warning: If node is nil it will disable for all nodes (and disable push on your other devices) */ +- (void) disablePushForServerJID:(XMPPJID*)serverJID + node:(nullable NSString*)node + elementId:(nullable NSString*)elementId { + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self performBlockAsync:^{ + NSString *eid = [self fixElementId:elementId]; + XMPPIQ *disableElement = [XMPPIQ disableNotificationsElementWithJID:serverJID node:node elementId:eid]; + [self.tracker addElement:disableElement block:^(XMPPIQ *responseIq, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + if (!responseIq || [responseIq isErrorIQ]) { + // timeout + XMPPLogWarn(@"disablePush error: %@ %@", disableElement, responseIq); + [strongSelf setRegistrationStatus:XMPPPushStatusError forServerJID:serverJID]; + [weakMulticast pushModule:strongSelf failedToDisablePushWithErrorIq:responseIq serverJID:serverJID node:node outgoingIq:disableElement]; + return; + } + [strongSelf setRegistrationStatus:XMPPPushStatusNotRegistered forServerJID:serverJID]; + [weakMulticast pushModule:strongSelf disabledPushForServerJID:serverJID node:node responseIq:responseIq outgoingIq:disableElement]; + } timeout:30]; + [self->xmppStream sendElement:disableElement]; + }]; +} + +#pragma mark Setup + +- (BOOL)activate:(XMPPStream *)aXmppStream +{ + if ([super activate:aXmppStream]) + { + [self performBlock:^{ + self->_registrationStatus = [NSMutableDictionary dictionary]; + self->_capabilitiesModules = [NSMutableSet set]; + [self->xmppStream autoAddDelegate:self delegateQueue:self->moduleQueue toModulesOfClass:[XMPPCapabilities class]]; + self->_tracker = [[XMPPIDTracker alloc] initWithStream:aXmppStream dispatchQueue:self->moduleQueue]; + + [self->xmppStream enumerateModulesWithBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) { + if ([module isKindOfClass:[XMPPCapabilities class]]) { + [self.capabilitiesModules addObject:(XMPPCapabilities*)module]; + } + }]; + }]; + return YES; + } + + return NO; +} + +- (void) deactivate { + [self performBlock:^{ + [self->_tracker removeAllIDs]; + self->_tracker = nil; + [self->xmppStream removeAutoDelegate:self delegateQueue:self->moduleQueue fromModulesOfClass:[XMPPCapabilities class]]; + self->_capabilitiesModules = nil; + self->_registrationStatus = nil; + }]; + [super deactivate]; +} + +#pragma mark XMPPStream Delegate + +- (void) refresh { + [self performBlockAsync:^{ + if (self->xmppStream.state != STATE_XMPP_CONNECTED) { + XMPPLogError(@"XMPPPushModule: refresh error - not connected. %@", self); + return; + } + [self.registrationStatus removeAllObjects]; + XMPPJID *jid = self->xmppStream.myJID.bareJID; + if (!jid) { return; } + __block BOOL supportsPush = NO; + __block NSXMLElement *capabilities = nil; + [self.capabilitiesModules enumerateObjectsUsingBlock:^(XMPPCapabilities * _Nonnull capsModule, BOOL * _Nonnull stop) { + id storage = capsModule.xmppCapabilitiesStorage; + BOOL fetched = [storage areCapabilitiesKnownForJID:jid xmppStream:self->xmppStream]; + if (fetched) { + capabilities = [storage capabilitiesForJID:jid xmppStream:self->xmppStream]; + if (capabilities) { + supportsPush = [self supportsPushFromCaps:capabilities]; + *stop = YES; + } + } else { + [capsModule fetchCapabilitiesForJID:jid]; + } + }]; + if (supportsPush) { + [self->multicastDelegate pushModule:self readyWithCapabilities:capabilities jid:jid]; + } + }]; +} + +- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender +{ + [self refresh]; +} + + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { + BOOL success = NO; + if (!iq.from) { + // Some error responses for self or contacts don't have a "from" + success = [self.tracker invokeForID:iq.elementID withObject:iq]; + } else { + success = [self.tracker invokeForElement:iq withObject:iq]; + } + //DDLogWarn(@"Could not match IQ: %@", iq); + return success; +} + +- (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module +{ + if (![module isKindOfClass:[XMPPCapabilities class]]) { + return; + } + [self performBlockAsync:^{ + [self.capabilitiesModules addObject:(XMPPCapabilities*)module]; + }]; + +} + +- (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module +{ + if (![module isKindOfClass:[XMPPCapabilities class]]) { + return; + } + [self performBlockAsync:^{ + [self.capabilitiesModules removeObject:(XMPPCapabilities*)module]; + }]; +} + +#pragma mark XMPPCapabilitiesDelegate + +- (void)xmppCapabilities:(XMPPCapabilities *)sender didDiscoverCapabilities:(NSXMLElement *)caps forJID:(XMPPJID *)jid { + XMPPLogVerbose(@"%@: %@\n%@:%@", THIS_FILE, THIS_METHOD, jid, caps); + NSString *myDomain = [self.xmppStream.myJID domain]; + if ([[jid bare] isEqualToString:[jid domain]]) { + if (![[jid domain] isEqualToString:myDomain]) { + // You're checking the server's capabilities but it's not your server(?) + return; + } + } else { + if (![[self.xmppStream.myJID bare] isEqualToString:[jid bare]]) { + // You're checking someone else's capabilities + return; + } + } + BOOL supportsXEP = [self supportsPushFromCaps:caps]; + if (supportsXEP) { + [multicastDelegate pushModule:self readyWithCapabilities:caps jid:jid]; + } +} + +#pragma mark Utility + +/** Generate elementId UUID if needed */ +- (nonnull NSString*) fixElementId:(nullable NSString*)elementId { + NSString *eid = nil; + if (!elementId.length) { + eid = [[NSUUID UUID] UUIDString]; + } else { + eid = [elementId copy]; + } + return eid; +} + +- (BOOL) supportsPushFromCaps:(NSXMLElement*)caps { + __block BOOL supportsPushXEP = NO; + NSArray *featureElements = [caps elementsForName:@"feature"]; + [featureElements enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *featureName = [obj attributeStringValueForName:@"var"]; + if ([featureName isEqualToString:XMPPPushXMLNS]){ + supportsPushXEP = YES; + *stop = YES; + } + }]; + return supportsPushXEP; +} + +@end + +@implementation XMPPPushOptions + +- (instancetype) initWithServerJID:(XMPPJID*)serverJID + node:(NSString*)node + formOptions:(NSDictionary*)formOptions { + NSParameterAssert(serverJID != nil); + NSParameterAssert(node != nil); + NSParameterAssert(formOptions != nil); + if (self = [super init]) { + _serverJID = [serverJID copy]; + _node = [node copy]; + _formOptions = [formOptions copy]; + } + return self; +} + +@end diff --git a/Extensions/XEP-0359/NSXMLElement+XEP_0359.h b/Extensions/XEP-0359/NSXMLElement+XEP_0359.h new file mode 100644 index 0000000000..dde3c8d490 --- /dev/null +++ b/Extensions/XEP-0359/NSXMLElement+XEP_0359.h @@ -0,0 +1,36 @@ +// +// NSXMLElement+XEP_0359.h +// XMPPFramework +// +// Created by Chris Ballinger on 10/10/17. +// Copyright © 2017 robbiehanson. All rights reserved. +// + +#import "XMPPElement.h" +#import "XMPPJID.h" + +NS_ASSUME_NONNULL_BEGIN + +/** 'urn:xmpp:sid:0' */ +extern NSString *const XMPPStanzaIdXmlns; +/** 'stanza-id' */ +extern NSString *const XMPPStanzaIdElementName; +/** 'origin-id' */ +extern NSString *const XMPPOriginIdElementName; + + +@interface NSXMLElement (XEP_0359) + +/** + * Some use cases require the originating entity, e.g. a client, to generate the stanza ID. In this case, the client MUST use the element extension element qualified by the 'urn:xmpp:sid:0' namespace. Note that originating entities often want to conceal their XMPP address and therefore the element has no 'by' attribute. + * + * Ex: + * + * @note This method will generate a NSUUID.uuidString for the 'id' attribute. + */ ++ (instancetype) originIdElement; +/** @note If nil is passed for uniqueId, this method will generate a NSUUID.uuidString for the 'id' attribute. */ ++ (instancetype) originIdElementWithUniqueId:(nullable NSString*)uniqueId; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0359/NSXMLElement+XEP_0359.m b/Extensions/XEP-0359/NSXMLElement+XEP_0359.m new file mode 100644 index 0000000000..56d66374e6 --- /dev/null +++ b/Extensions/XEP-0359/NSXMLElement+XEP_0359.m @@ -0,0 +1,32 @@ +// +// NSXMLElement+XEP_0359.m +// XMPPFramework +// +// Created by Chris Ballinger on 10/10/17. +// Copyright © 2017 robbiehanson. All rights reserved. +// + +#import "NSXMLElement+XEP_0359.h" +#import "NSXMLElement+XMPP.h" + +NSString *const XMPPStanzaIdXmlns = @"urn:xmpp:sid:0"; +NSString *const XMPPStanzaIdElementName = @"stanza-id"; +NSString *const XMPPOriginIdElementName = @"origin-id"; + +@implementation NSXMLElement (XEP_0359) + ++ (instancetype) originIdElement { + return [self originIdElementWithUniqueId:nil]; +} + ++ (instancetype) originIdElementWithUniqueId:(nullable NSString*)uniqueId { + if (!uniqueId) { + uniqueId = [NSUUID UUID].UUIDString; + } + NSXMLElement *element = [NSXMLElement elementWithName:XMPPOriginIdElementName xmlns:XMPPStanzaIdXmlns]; + [element addAttributeWithName:@"id" objectValue:uniqueId]; + + return element; +} + +@end diff --git a/Extensions/XEP-0359/XMPPCapabilities+XEP_0359.h b/Extensions/XEP-0359/XMPPCapabilities+XEP_0359.h new file mode 100644 index 0000000000..e96bc36a7e --- /dev/null +++ b/Extensions/XEP-0359/XMPPCapabilities+XEP_0359.h @@ -0,0 +1,25 @@ +// +// XMPPCapabilities+XEP_0359.h +// XMPPFramework +// +// Created by Chris Ballinger on 10/11/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPCapabilities.h" +#import "XMPPMessage.h" + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPCapabilities (XEP_0359) + +/** + * Whether or not the stanza-id can be trusted. + * + * Before processing the stanza ID of a message and using it for deduplication purposes or for MAM catchup, the receiving entity SHOULD ensure that the stanza ID could not have been faked, by verifying that the entity referenced in the by attribute does annouce the 'urn:xmpp:sid:0' namespace in its disco features. + * + * The value of the 'by' attribute MUST be the XMPP address of the entity assigning the unique and stable stanza ID. For one-on-one messages the assigning entity is the account. In groupchats the assigning entity is the room. Note that XMPP addresses are normalized as defined in RFC 6122 [4]. + */ +- (BOOL) hasValidStanzaId:(XMPPMessage*)message; + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0359/XMPPCapabilities+XEP_0359.m b/Extensions/XEP-0359/XMPPCapabilities+XEP_0359.m new file mode 100644 index 0000000000..212cbaf4c8 --- /dev/null +++ b/Extensions/XEP-0359/XMPPCapabilities+XEP_0359.m @@ -0,0 +1,62 @@ +// +// XMPPCapabilities+XEP_0359.m +// XMPPFramework +// +// Created by Chris Ballinger on 10/11/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPCapabilities+XEP_0359.h" +#import "XMPPMessage+XEP0045.h" +#import "XMPPMessage+XEP_0359.h" +#import "NSXMLElement+XEP_0359.h" +#import "XMPPMessage+XEP_0184.h" + +@implementation XMPPCapabilities (XEP_0359) + +- (BOOL) hasValidStanzaId:(XMPPMessage*)message { + if (!message) { return NO; } + NSDictionary *stanzaIds = message.stanzaIds; + if (!stanzaIds.count) { return NO; } + + XMPPJID *expectedBy = nil; + if (message.isGroupChatMessage) { + expectedBy = message.from.bareJID; + } else { + expectedBy = self.xmppStream.myJID.bareJID; + } + if (!expectedBy) { return NO; } + + // The value of the 'by' attribute MUST be the XMPP address of the entity assigning the unique and stable stanza ID. For one-on-one messages the assigning entity is the account. In groupchats the assigning entity is the room. Note that XMPP addresses are normalized as defined in RFC 6122 [4]. + + NSString *stanzaId = stanzaIds[expectedBy]; + if (!stanzaId.length) { + return NO; + } + + // Before processing the stanza ID of a message and using it for deduplication purposes or for MAM catchup, the receiving entity SHOULD ensure that the stanza ID could not have been faked, by verifying that the entity referenced in the by attribute does annouce the 'urn:xmpp:sid:0' namespace in its disco features. + NSXMLElement *caps = [self.xmppCapabilitiesStorage capabilitiesForJID:expectedBy xmppStream:self.xmppStream]; + if (!caps) { return NO; } + BOOL supportsStanzaIdFromCaps = [self supportsStanzaIdFromCaps:caps]; + if (!supportsStanzaIdFromCaps) { + return NO; + } + + // We've passed all the checks + return YES; +} + +- (BOOL) supportsStanzaIdFromCaps:(NSXMLElement*)caps { + __block BOOL supportsStanzaId = NO; + NSArray *featureElements = [caps elementsForName:@"feature"]; + [featureElements enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *featureName = [obj attributeStringValueForName:@"var"]; + if ([featureName isEqualToString:XMPPStanzaIdXmlns]){ + supportsStanzaId = YES; + *stop = YES; + } + }]; + return supportsStanzaId; +} + +@end diff --git a/Extensions/XEP-0359/XMPPMessage+XEP_0359.h b/Extensions/XEP-0359/XMPPMessage+XEP_0359.h new file mode 100644 index 0000000000..01e9accd58 --- /dev/null +++ b/Extensions/XEP-0359/XMPPMessage+XEP_0359.h @@ -0,0 +1,56 @@ +// +// XMPPMessage+XEP_0359.h +// XMPPFramework +// +// Created by Chris Ballinger on 10/10/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPMessage.h" +#import "XMPPElement.h" +#import "XMPPJID.h" +#import "XMPPCapabilities.h" + +/** + * This XEP introduces unique and stable IDs for messages, which are beneficial in various ways. For example, they can be used together with Message Archive Management (XEP-0313) [1] to uniquely identify a message within an archive. They are also useful in the context of Multi-User Chat (XEP-0045) [2] conferences, as they allow to identify a message reflected by a MUC service back to the originating entity. + * https://xmpp.org/extensions/xep-0359.html + */ + +NS_ASSUME_NONNULL_BEGIN +@class XMPPStanzaId; +@interface XMPPMessage (XEP_0359) + +/** + * XEP-0359: Origin Id + * + * Usually this will be the same as the XMPPElement elementID, if present. + * It is intended to be a unique identifier, useful for deduplication for MAM and MUC. + */ +@property (nonatomic, readonly, nullable) NSString *originId; + +/** + * XEP-0359: Origin Id + * + * This usually should be the same as the XMPPElement elementID. + * It must be a unique identifier (UUID), and is useful for deduplication for MAM and MUC. + * + * @note If nil is passed for uniqueId, this method will generate a NSUUID.uuidString for the 'id' attribute. + */ +- (void) addOriginId:(nullable NSString*)originId; + +/** + * XEP-0359: Stanza Ids + * + * Usually there will be just one stanzaId, if supported, + * but in the case of message receipts there can be more. + * It will be a unique identifier, useful for deduplication for MAM and MUC. + * + * @note key=by, value=id + * + * @warn ⚠️ Do not trust this value without first checking XMPPCapabilities hasValidStanzaId: + */ +@property (nonatomic, readonly) NSDictionary *stanzaIds; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0359/XMPPMessage+XEP_0359.m b/Extensions/XEP-0359/XMPPMessage+XEP_0359.m new file mode 100644 index 0000000000..5d0df5079e --- /dev/null +++ b/Extensions/XEP-0359/XMPPMessage+XEP_0359.m @@ -0,0 +1,44 @@ +// +// XMPPMessage+XEP_0359.m +// XMPPFramework +// +// Created by Chris Ballinger on 10/10/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPMessage+XEP_0359.h" +#import "NSXMLElement+XEP_0359.h" +#import "NSXMLElement+XMPP.h" + +@implementation XMPPMessage (XEP_0359) + +- (NSString*) originId { + NSXMLElement *oid = [self elementForName:XMPPOriginIdElementName xmlns:XMPPStanzaIdXmlns]; + return [oid attributeStringValueForName:@"id"]; +} + +- (void) addOriginId:(nullable NSString*)originId { + NSXMLElement *oid = [NSXMLElement originIdElementWithUniqueId:originId]; + [self addChild:oid]; +} + +- (NSDictionary*) stanzaIds { + NSArray *stanzaIdElements = [self elementsForLocalName:XMPPStanzaIdElementName URI:XMPPStanzaIdXmlns]; + if (!stanzaIdElements.count) { return @{}; } + + NSMutableDictionary *stanzaIds = [NSMutableDictionary dictionaryWithCapacity:stanzaIdElements.count]; + + [stanzaIdElements enumerateObjectsUsingBlock:^void(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *sid = [obj attributeStringValueForName:@"id"]; + if (!sid) { return; } + NSString *by = [obj attributeStringValueForName:@"by"]; + if (!by.length) { return; } + XMPPJID *jid = [XMPPJID jidWithString:by]; + if (!jid) { return; } + [stanzaIds setObject:sid forKey:jid]; + }]; + + return stanzaIds; +} + +@end diff --git a/Extensions/XEP-0359/XMPPStanzaIdModule.h b/Extensions/XEP-0359/XMPPStanzaIdModule.h new file mode 100644 index 0000000000..0d7734baa6 --- /dev/null +++ b/Extensions/XEP-0359/XMPPStanzaIdModule.h @@ -0,0 +1,45 @@ +// +// XMPPStanzaIdModule.h +// XMPPFramework +// +// Created by Chris Ballinger on 10/14/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPModule.h" +#import "XMPPMessage.h" + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPStanzaIdModule : XMPPModule + +/** + * Automatically add origin-id to outgoing messages. + * If there is already an originId present, we will keep that one. + * + * Default: YES + */ +@property (atomic, readwrite) BOOL autoAddOriginId; + +/** + * Copy elementId to originId if present, otherwise generate new UUID + * Disable this if your elementIds aren't globally unique. + * Has no effect if autoAddOriginId is disabled. + * + * Default: YES + */ +@property (atomic, readwrite) BOOL copyElementIdIfPresent; + + +/** + * Return NO in this block to prevent origin-id from being added to a specific message. + */ +@property (atomic, nullable, copy) BOOL (^filterBlock)(XMPPStream *stream, XMPPMessage* message); + +@end + +@protocol XMPPStanzaIdDelegate +- (void) stanzaIdModule:(XMPPStanzaIdModule*)sender + didAddOriginId:(NSString*)originId + toMessage:(XMPPMessage*)message; +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0359/XMPPStanzaIdModule.m b/Extensions/XEP-0359/XMPPStanzaIdModule.m new file mode 100644 index 0000000000..78ca71e714 --- /dev/null +++ b/Extensions/XEP-0359/XMPPStanzaIdModule.m @@ -0,0 +1,123 @@ +// +// XMPPStanzaIdModule.m +// XMPPFramework +// +// Created by Chris Ballinger on 10/14/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +#import "XMPPStanzaIdModule.h" +#import "XMPPMessage.h" +#import "XMPPMessage+XEP_0359.h" +#import "XMPPRoom.h" + +@implementation XMPPStanzaIdModule +// MARK: Setup +@synthesize autoAddOriginId = _autoAddOriginId; +@synthesize copyElementIdIfPresent = _copyElementIdIfPresent; +@synthesize filterBlock = _filterBlock; + +- (instancetype) initWithDispatchQueue:(dispatch_queue_t)queue { + if (self = [super initWithDispatchQueue:queue]) { + _autoAddOriginId = YES; + _copyElementIdIfPresent = YES; + } + return self; +} + +// MARK: Properties + +- (void) setAutoAddOriginId:(BOOL)autoAddOriginId { + [self performBlockAsync:^{ + self->_autoAddOriginId = autoAddOriginId; + }]; +} + +- (BOOL) autoAddOriginId { + __block BOOL autoAddOriginId = NO; + [self performBlock:^{ + autoAddOriginId = self->_autoAddOriginId; + }]; + return autoAddOriginId; +} + +- (BOOL) copyElementIdIfPresent { + __block BOOL copyElementIdIfPresent = NO; + [self performBlock:^{ + copyElementIdIfPresent = self->_copyElementIdIfPresent; + }]; + return copyElementIdIfPresent; +} + +- (void) setCopyElementIdIfPresent:(BOOL)copyElementIdIfPresent { + [self performBlockAsync:^{ + self->_copyElementIdIfPresent = copyElementIdIfPresent; + }]; +} + +- (void) setFilterBlock:(BOOL (^)(XMPPStream *stream, XMPPMessage* message))filterBlock { + [self performBlockAsync:^{ + self->_filterBlock = [filterBlock copy]; + }]; +} + +- (BOOL (^)(XMPPStream *stream, XMPPMessage* message))filterBlock { + __block BOOL (^filterBlock)(XMPPStream *stream, XMPPMessage* message) = nil; + [self performBlock:^{ + filterBlock = self->_filterBlock; + }]; + return filterBlock; +} + +/** Returning YES means message is omitted from further processing */ +- (BOOL) shouldFilterMessage:(XMPPMessage*)message { + // Attaching origin-id to MUC invite elements is rejected by some servers + // Possibly other stanzas as well + NSXMLElement *mucUserElement = [message elementForName:@"x" xmlns:XMPPMUCUserNamespace]; + if ([mucUserElement elementForName:@"invite"]) { + return YES; + } + return NO; +} + +// MARK: XMPPStreamDelegate + +- (nullable XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message { + // Do not add originId if already present, + // or if autoAddOriginId is disabled + if (message.originId.length || + !self.autoAddOriginId) { + return message; + } + + // Filter out message types known not to work with origin-id + if ([self shouldFilterMessage:message]) { + return message; + } + + // User-filtering of messages + BOOL (^filterBlock)(XMPPStream *stream, XMPPMessage* message) = self.filterBlock; + if (filterBlock && !filterBlock(sender, message)) { + return message; + } + + NSString *originId = [NSUUID UUID].UUIDString; + + // Copy existing elementId if desired, + // otherwise use the new UUID + NSString *elementId = message.elementID; + if (elementId.length && + self.copyElementIdIfPresent) { + originId = elementId; + } + + [message addOriginId:originId]; + + [self performBlockAsync:^{ + [self->multicastDelegate stanzaIdModule:self didAddOriginId:originId toMessage:message]; + }]; + + return message; +} + +@end diff --git a/Extensions/XEP-0363/XMPPHTTPFileUpload.h b/Extensions/XEP-0363/XMPPHTTPFileUpload.h new file mode 100644 index 0000000000..e6690346bf --- /dev/null +++ b/Extensions/XEP-0363/XMPPHTTPFileUpload.h @@ -0,0 +1,92 @@ +// +// XMPPHTTPFileUpload.h +// Mangosta +// +// Created by Andres Canal on 5/19/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPSlot.h" +#import "XMPPModule.h" + +NS_ASSUME_NONNULL_BEGIN + +/** urn:xmpp:http:upload */ +extern NSString *const XMPPHTTPFileUploadNamespace; + +@interface XMPPHTTPFileUpload : XMPPModule + +/** + * @note When using the block-based APIs, the delegate methods will not fire. + * @note completion will default to moduleQueue + */ +- (void)requestSlotFromService:(XMPPJID*)serviceJID + filename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*)contentType + completion:(void (^_Nonnull)(XMPPSlot * _Nullable slot, XMPPIQ * _Nullable responseIq, NSError * _Nullable error))completion; + +/** + * @note When using the block-based APIs, the delegate methods will not fire. + * @note completion will default to moduleQueue + */ +- (void)requestSlotFromService:(XMPPJID*)serviceJID + filename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*)contentType + completion:(void (^_Nonnull)(XMPPSlot * _Nullable slot, XMPPIQ * _Nullable responseIq, NSError * _Nullable error))completion + completionQueue:(_Nullable dispatch_queue_t)completionQueue; + +/** Tag will be passed back from delegate methods */ +- (void)requestSlotFromService:(XMPPJID*)serviceJID + filename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*)contentType + tag:(nullable id)tag; + +@property (nullable, nonatomic, readonly, copy) NSString *serviceName DEPRECATED_MSG_ATTRIBUTE("XMPPHTTPFileUpload can now handle multiple services. Use requestSlotFromService:filename:size:contentType: instead."); + +@end + +@protocol XMPPHTPPFileUploadDelegate +@optional + +- (void)xmppHTTPFileUpload:(XMPPHTTPFileUpload *)sender service:(XMPPJID*)service didAssignSlot:(XMPPSlot *)slot response:(XMPPIQ*)response tag:(nullable id)tag; + +- (void)xmppHTTPFileUpload:(XMPPHTTPFileUpload *)sender service:(XMPPJID*)service didFailToAssignSlotWithError:(NSError*)error response:(nullable XMPPIQ*)response tag:(nullable id)tag; + +- (void)xmppHTTPFileUpload:(XMPPHTTPFileUpload *)sender didAssignSlot:(XMPPSlot *)slot DEPRECATED_MSG_ATTRIBUTE("XMPPHTPPFileUploadDelegate now handles multiple services. Use xmppHTTPFileUpload:service:didAssignSlot: instead."); +- (void)xmppHTTPFileUpload:(XMPPHTTPFileUpload *)sender didFailToAssignSlotWithError:(nullable XMPPIQ *) iqError DEPRECATED_MSG_ATTRIBUTE("XMPPHTPPFileUploadDelegate now handles multiple services. Use xmppHTTPFileUpload:service:didFailToAssignSlotWithError: instead."); + +@end + + +// MARK: - Error Handling + +extern NSString *const XMPPHTTPFileUploadErrorDomain; + +typedef NS_ENUM(NSInteger, XMPPHTTPFileUploadErrorCode) { + /** Catchall for any other errors */ + XMPPHTTPFileUploadErrorCodeUnknown = 0, + /** This will happen whenever XMPPSlot is nil */ + XMPPHTTPFileUploadErrorCodeBadResponse, + /** This happens if result is nil or timeout */ + XMPPHTTPFileUploadErrorCodeNoResponse, +}; + +extern NSString* StringForXMPPHTTPFileUploadErrorCode(XMPPHTTPFileUploadErrorCode errorCode); + +// MARK: - Deprecated + +@interface XMPPHTTPFileUpload (Deprecated) + +- (instancetype)initWithServiceName:(NSString *)serviceName DEPRECATED_MSG_ATTRIBUTE("XMPPHTTPFileUpload can now handle multiple services. Use requestSlotFromService:filename:size:contentType: instead."); +- (instancetype)initWithServiceName:(NSString *)serviceName dispatchQueue:(nullable dispatch_queue_t)queue DEPRECATED_MSG_ATTRIBUTE("XMPPHTTPFileUpload can now handle multiple services. Use requestSlotFromService:filename:size:contentType: instead."); + +- (void)requestSlotForFilename:(NSString *)filename + size:(NSUInteger)size + contentType:(NSString *)contentType DEPRECATED_MSG_ATTRIBUTE("XMPPHTTPFileUpload can now handle multiple services. Use requestSlotFromService:filename:size:contentType: instead."); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0363/XMPPHTTPFileUpload.m b/Extensions/XEP-0363/XMPPHTTPFileUpload.m new file mode 100644 index 0000000000..6c82170e72 --- /dev/null +++ b/Extensions/XEP-0363/XMPPHTTPFileUpload.m @@ -0,0 +1,229 @@ +// +// XMPPHTTPFileUpload.m +// Mangosta +// +// Created by Andres Canal on 5/19/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPHTTPFileUpload.h" +#import "XMPPStream.h" +#import "NSXMLElement+XMPP.h" +#import "XMPPIDTracker.h" +#import "XMPPLogging.h" + +// Log levels: off, error, warn, info, verbose +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_SEND_RECV; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +NSString *const XMPPHTTPFileUploadNamespace = @"urn:xmpp:http:upload"; +NSString *const XMPPHTTPFileUploadErrorDomain = @"XMPPHTTPFileUploadErrorDomain"; + +NSString* StringForXMPPHTTPFileUploadErrorCode(XMPPHTTPFileUploadErrorCode errorCode) { + switch (errorCode) { + case XMPPHTTPFileUploadErrorCodeUnknown: + return @"Unknown Error"; + case XMPPHTTPFileUploadErrorCodeNoResponse: + return @"No Response"; + case XMPPHTTPFileUploadErrorCodeBadResponse: + return @"Bad Response"; + } +} + +static NSError *ErrorForCode(XMPPHTTPFileUploadErrorCode errorCode) { + NSString *description = StringForXMPPHTTPFileUploadErrorCode(errorCode); + return [NSError errorWithDomain:XMPPHTTPFileUploadErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: description}]; +} + +@interface XMPPHTTPFileUpload() +@property (nonatomic, strong, readonly) XMPPIDTracker *responseTracker; +@end + +@implementation XMPPHTTPFileUpload + +- (BOOL)activate:(XMPPStream *)aXmppStream { + + if ([super activate:aXmppStream]) { + _responseTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue]; + + return YES; + } + + return NO; +} + +- (void)deactivate { + dispatch_block_t block = ^{ @autoreleasepool { + + [self.responseTracker removeAllIDs]; + self->_responseTracker = nil; + + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + [super deactivate]; +} + +- (void)requestSlotFromService:(XMPPJID*)serviceJID + filename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*)contentType + tag:(nullable id)tag { + __weak typeof(self) weakSelf = self; + __weak id weakMulticast = multicastDelegate; + [self requestSlotFromService:serviceJID filename:filename size:size contentType:contentType completion:^(XMPPSlot * _Nullable slot, XMPPIQ * _Nullable resultIq, NSError * _Nullable error) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (!slot) { + [weakMulticast xmppHTTPFileUpload:strongSelf service:serviceJID didFailToAssignSlotWithError:error response:resultIq tag:tag]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [weakMulticast xmppHTTPFileUpload:strongSelf didFailToAssignSlotWithError:resultIq]; +#pragma clang diagnostic pop + } else { + [weakMulticast xmppHTTPFileUpload:strongSelf service:serviceJID didAssignSlot:slot response:resultIq tag:tag]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [weakMulticast xmppHTTPFileUpload:strongSelf didAssignSlot:slot]; +#pragma clang diagnostic pop + } + }]; +} + +- (void)requestSlotFromService:(XMPPJID*)serviceJID + filename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*)contentType + completion:(void (^_Nonnull)(XMPPSlot * _Nullable slot, XMPPIQ * _Nullable resultIq, NSError * _Nullable error))completion { + [self requestSlotFromService:serviceJID filename:filename size:size contentType:contentType completion:completion completionQueue:nil]; +} + +- (void)requestSlotFromService:(XMPPJID*)serviceJID + filename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*)contentType + completion:(void (^_Nonnull)(XMPPSlot * _Nullable slot, XMPPIQ * _Nullable resultIq, NSError * _Nullable error))completion + completionQueue:(_Nullable dispatch_queue_t)completionQueue { + NSParameterAssert(filename != nil); + NSParameterAssert(contentType != nil); + NSParameterAssert(size > 0); + NSParameterAssert(serviceJID != nil); + NSParameterAssert(completion != nil); + if (!completion) { + XMPPLogError(@"XMPPHTTPFileUpload: No completion block specified, aborting..."); + return; + } + if (!completionQueue) { + completionQueue = moduleQueue; + } + dispatch_block_t block = ^{ @autoreleasepool { + + // + // + // my_juliet.png + // 23456 + // image/jpeg + // + // + + NSString *iqID = [XMPPStream generateUUID]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:serviceJID elementID:iqID]; + + XMPPElement *request = [XMPPElement elementWithName:@"request"]; + [request setXmlns:XMPPHTTPFileUploadNamespace]; + if (filename) { + [request addChild:[XMPPElement elementWithName:@"filename" stringValue:filename]]; + } + [request addChild:[XMPPElement elementWithName:@"size" numberValue:[NSNumber numberWithUnsignedInteger:size]]]; + if (contentType) { + [request addChild:[XMPPElement elementWithName:@"content-type" stringValue:contentType]]; + } + + [iq addChild:request]; + + __weak typeof(self) weakSelf = self; + [self.responseTracker addID:iqID block:^(id obj, id info) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { return; } + NSError *error = nil; + XMPPIQ *responseIq = nil; + XMPPSlot *slot = nil; + if ([obj isKindOfClass:[XMPPIQ class]]) { + responseIq = obj; + if ([responseIq isResultIQ]) { + slot = [[XMPPSlot alloc] initWithIQ:responseIq]; + } + if (!slot) { + error = ErrorForCode(XMPPHTTPFileUploadErrorCodeBadResponse); + } + } else { + error = ErrorForCode(XMPPHTTPFileUploadErrorCodeNoResponse); + } + if (!slot && !error) { + error = ErrorForCode(XMPPHTTPFileUploadErrorCodeUnknown); + } + + dispatch_async(completionQueue, ^{ + completion(slot, responseIq, error); + }); + } timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq +{ + NSString *type = [iq type]; + + if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]) + { + return [self.responseTracker invokeForID:[iq elementID] withObject:iq]; + } + + return NO; +} + +@end + +@implementation XMPPHTTPFileUpload (Deprecated) + +- (instancetype)initWithServiceName:(NSString *)serviceName { + return [self initWithServiceName:serviceName dispatchQueue:nil]; +} + +- (instancetype)initWithServiceName:(NSString *)serviceName dispatchQueue:(dispatch_queue_t)queue { + NSParameterAssert(serviceName != nil); + + if ((self = [super initWithDispatchQueue:queue])){ + _serviceName = [serviceName copy]; + } + + return self; +} + +- (void)requestSlotForFilename:(NSString*)filename + size:(NSUInteger)size + contentType:(NSString*) contentType { + XMPPJID *uploadService = [XMPPJID jidWithString:self.serviceName]; + NSParameterAssert(uploadService != nil); + if (!uploadService) { return; } + [self requestSlotFromService:uploadService filename:filename size:size contentType:contentType tag:nil]; +} + +@end diff --git a/Extensions/XEP-0363/XMPPSlot.h b/Extensions/XEP-0363/XMPPSlot.h new file mode 100644 index 0000000000..61411abd38 --- /dev/null +++ b/Extensions/XEP-0363/XMPPSlot.h @@ -0,0 +1,39 @@ +// +// XMPPSlot.h +// Mangosta +// +// Created by Andres Canal on 5/19/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import +#import "XMPPJID.h" +#import "XMPPIQ.h" + +@import KissXML; + +NS_ASSUME_NONNULL_BEGIN +@interface XMPPSlot: NSObject + +/** Convenience property for putURL + putHeaders */ +@property (nonatomic, readonly) NSURLRequest *putRequest; + +/** HTTP headers, for example Authorization. name=value */ +@property (nonatomic, readonly) NSDictionary *putHeaders; +@property (nonatomic, readonly) NSURL *putURL; +@property (nonatomic, readonly) NSURL *getURL; + +- (instancetype)initWithPutURL:(NSURL *)putURL getURL:(NSURL *)getURL putHeaders:(nullable NSDictionary*)putHeaders NS_DESIGNATED_INITIALIZER; + +/** Will return nil if iq does not contain slot */ +- (nullable instancetype)initWithIQ:(XMPPIQ *)iq; + +/** Not available, use designated initializer */ +- (instancetype) init NS_UNAVAILABLE; + +@property (nonatomic, readonly) NSString *put DEPRECATED_MSG_ATTRIBUTE("Use putURL instead."); +@property (nonatomic, readonly) NSString *get DEPRECATED_MSG_ATTRIBUTE("Use getURL instead."); +- (nullable instancetype)initWithPut:(NSString *)put andGet:(NSString *)get DEPRECATED_MSG_ATTRIBUTE("Use initWithPutURL:getURL:putHeaders: instead."); + +@end +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0363/XMPPSlot.m b/Extensions/XEP-0363/XMPPSlot.m new file mode 100644 index 0000000000..153aaf6fd8 --- /dev/null +++ b/Extensions/XEP-0363/XMPPSlot.m @@ -0,0 +1,100 @@ +// +// XMPPSlot.m +// Mangosta +// +// Created by Andres Canal on 5/19/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPSlot.h" +#import "NSXMLElement+XMPP.h" + +@implementation XMPPSlot + +- (instancetype) init { + NSAssert(NO, @"Use designated initializer."); + return nil; +} + +- (instancetype)initWithPutURL:(NSURL *)putURL getURL:(NSURL *)getURL putHeaders:(nullable NSDictionary *)putHeaders { + NSParameterAssert(putURL != nil); + NSParameterAssert(getURL != nil); + if (self = [super init]) { + _putURL = [putURL copy]; + _getURL = [getURL copy]; + if (putHeaders) { + _putHeaders = [putHeaders copy]; + } else { + _putHeaders = @{}; + } + } + return self; +} + +- (nullable instancetype)initWithPut:(NSString *)put andGet:(NSString *)get { + NSParameterAssert(put != nil); + NSParameterAssert(get != nil); + if (!put || !get) { + return nil; + } + NSURL *putURL = [NSURL URLWithString:put]; + NSURL *getURL = [NSURL URLWithString:get]; + if (!putURL || !getURL) { + return nil; + } + return [self initWithPutURL:putURL getURL:getURL putHeaders:@{}]; +} + +- (nullable instancetype)initWithIQ:(XMPPIQ *)iq { + NSParameterAssert(iq != nil); + NSXMLElement *slot = [iq elementForName:@"slot"]; + NSXMLElement *putElement = [slot elementForName:@"put"]; + NSXMLElement *getElement = [slot elementForName:@"get"]; + + // Early versions of the spec didn't support headers + // See https://xmpp.org/extensions/xep-0363.html + NSString *putURLString = [putElement attributeStringValueForName:@"url"]; + if (!putURLString) { + putURLString = putElement.stringValue; + } + NSString *getURLString = [getElement attributeStringValueForName:@"url"]; + if (!getURLString) { + getURLString = getElement.stringValue; + } + + if (!putURLString || !getURLString) { + return nil; + } + NSURL *putURL = [NSURL URLWithString:putURLString]; + NSURL *getURL = [NSURL URLWithString:getURLString]; + if (!putURL || !getURL) { + return nil; + } + NSArray *headers = [putElement elementsForName:@"header"]; + NSMutableDictionary *putHeaders = [NSMutableDictionary dictionaryWithCapacity:headers.count]; + [headers enumerateObjectsUsingBlock:^(NSXMLElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *name = [obj attributeStringValueForName:@"name"]; + NSString *value = obj.stringValue; + if (name && value) { + [putHeaders setObject:value forKey:name]; + } + }]; + return [self initWithPutURL:putURL getURL:getURL putHeaders:putHeaders]; +} + +- (NSURLRequest*) putRequest { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.putURL]; + request.HTTPMethod = @"PUT"; + [request setAllHTTPHeaderFields:self.putHeaders]; + return request; +} + +- (NSString*) put { + return self.putURL.absoluteString; +} + +- (NSString*) get { + return self.getURL.absoluteString; +} + +@end diff --git a/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLight.xcdatamodel/contents b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLight.xcdatamodel/contents new file mode 100644 index 0000000000..6f10c8d34d --- /dev/null +++ b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLight.xcdatamodel/contents @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.h b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.h new file mode 100644 index 0000000000..387b036d2a --- /dev/null +++ b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.h @@ -0,0 +1,18 @@ +// +// XMPPRoomLightCoreDataStorage.h +// Mangosta +// +// Created by Andres Canal on 6/8/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPP.h" +#import "XMPPCoreDataStorage.h" +#import "XMPPRoomLight.h" + +@interface XMPPRoomLightCoreDataStorage : XMPPCoreDataStorage + +- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room; +- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room; + +@end diff --git a/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.m b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.m new file mode 100644 index 0000000000..0a7eff984c --- /dev/null +++ b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.m @@ -0,0 +1,122 @@ +// +// XMPPRoomLightCoreDataStorage.m +// Mangosta +// +// Created by Andres Canal on 6/8/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPRoomLightCoreDataStorage.h" +#import "XMPPCoreDataStorageProtected.h" +#import "NSXMLElement+XEP_0203.h" +#import "XMPPRoomLightMessageCoreDataStorageObject.h" + +@implementation XMPPRoomLightCoreDataStorage{ + NSString *messageEntityName; +} + +- (void)commonInit{ + + [super commonInit]; + + messageEntityName = NSStringFromClass([XMPPRoomLightMessageCoreDataStorageObject class]); + +} + +- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room{ + XMPPStream *xmppStream = room.xmppStream; + + XMPPJID *roomFromUser = [XMPPJID jidWithString:[message from].resource]; + XMPPJID *myUser = [room.xmppStream myJID]; + + if([roomFromUser isEqualToJID:myUser options:XMPPJIDCompareBare]) { + // room is broadcasting back a message that has already been handled as outgoing + return; + } + + [self scheduleBlock:^{ + [self insertMessage:message outgoing:NO remoteTimestamp:nil forRoom:room stream:xmppStream]; + }]; +} + +- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room{ + XMPPStream *xmppStream = room.xmppStream; + + [self scheduleBlock:^{ + [self insertMessage:message outgoing:YES remoteTimestamp:nil forRoom:room stream:xmppStream]; + }]; +} + +- (void)insertMessage:(XMPPMessage *)message + outgoing:(BOOL)isOutgoing + remoteTimestamp:(NSDate *)remoteTimestamp + forRoom:(XMPPRoomLight *)room + stream:(XMPPStream *)xmppStream{ + // Extract needed information + + XMPPJID *myRoomJID = [XMPPJID jidWithUser:room.roomJID.user + domain:room.roomJID.domain + resource:xmppStream.myJID.bare]; + + XMPPJID *roomJID = room.roomJID; + XMPPJID *messageJID = isOutgoing ? myRoomJID : [message from]; + + NSDate *localTimestamp = remoteTimestamp ?: [[NSDate alloc] init]; + + NSString *messageBody = [[message elementForName:@"body"] stringValue]; + + NSManagedObjectContext *moc = [self managedObjectContext]; + NSString *streamBareJidStr = [[self myJIDForXMPPStream:xmppStream] bare]; + + NSEntityDescription *messageEntity = [self messageEntity:moc]; + + XMPPRoomLightMessageCoreDataStorageObject *roomMessage = (XMPPRoomLightMessageCoreDataStorageObject *) + [[NSManagedObject alloc] initWithEntity:messageEntity insertIntoManagedObjectContext:nil]; + + roomMessage.message = message; + roomMessage.roomJID = roomJID; + roomMessage.jid = messageJID; + roomMessage.nickname = [messageJID resource]; + roomMessage.body = messageBody; + roomMessage.localTimestamp = localTimestamp; + roomMessage.remoteTimestamp = remoteTimestamp; + roomMessage.isFromMe = isOutgoing; + roomMessage.streamBareJidStr = streamBareJidStr; + + [moc insertObject:roomMessage]; + [self didInsertMessage:roomMessage]; +} + +- (void)didInsertMessage:(XMPPRoomLightMessageCoreDataStorageObject *)message{ + // Override me if you're extending the XMPPRoomLightMessageCoreDataStorageObject class to add additional properties. + // You can update your additional properties here. + // + // At this point the standard properties have already been set. + // So you can, for example, access the XMPPMessage via message.message. +} + +- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc{ + + // This method should be thread-safe. + // So be sure to access the entity name through the property accessor. + + return [NSEntityDescription entityForName:[self messageEntityName] inManagedObjectContext:moc]; +} + +- (NSString *)messageEntityName{ + + __block NSString *result = nil; + + dispatch_block_t block = ^{ + result = self->messageEntityName; + }; + + if (dispatch_get_specific(storageQueueTag)) + block(); + else + dispatch_sync(storageQueue, block); + + return result; +} + +@end diff --git a/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorageProtected.h b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorageProtected.h new file mode 100644 index 0000000000..92da32d2f1 --- /dev/null +++ b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorageProtected.h @@ -0,0 +1,14 @@ +#import "XMPPRoomLightCoreDataStorage.h" +#import "XMPPCoreDataStorageProtected.h" + +@interface XMPPRoomLightCoreDataStorage (Protected) + +- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc; + +- (void)insertMessage:(XMPPMessage *)message + outgoing:(BOOL)isOutgoing + remoteTimestamp:(NSDate *)remoteTimestamp + forRoom:(XMPPRoomLight *)room + stream:(XMPPStream *)xmppStream; + +@end diff --git a/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightMessageCoreDataStorageObject.h b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightMessageCoreDataStorageObject.h new file mode 100644 index 0000000000..69ef05b8e6 --- /dev/null +++ b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightMessageCoreDataStorageObject.h @@ -0,0 +1,46 @@ +#import +#import + +#import "XMPPRoomLight.h" +#import "XMPPRoomMessage.h" + +@interface XMPPRoomLightMessageCoreDataStorageObject : NSManagedObject + +/** + * The properties below are documented in the XMPPRoomMessage protocol. +**/ + +@property (nonatomic, retain) XMPPMessage * message; // Transient (proper type, not on disk) +@property (nonatomic, retain) NSString * messageStr; // Shadow (binary data, written to disk) + +@property (nonatomic, strong) XMPPJID * roomJID; // Transient (proper type, not on disk) +@property (nonatomic, strong) NSString * roomJIDStr; // Shadow (binary data, written to disk) + +@property (nonatomic, retain) XMPPJID * jid; // Transient (proper type, not on disk) +@property (nonatomic, retain) NSString * jidStr; // Shadow (binary data, written to disk) + +@property (nonatomic, retain) NSString * nickname; +@property (nonatomic, retain) NSString * body; + +@property (nonatomic, retain) NSDate * localTimestamp; +@property (nonatomic, strong) NSDate * remoteTimestamp; + +@property (nonatomic, assign) BOOL isFromMe; +@property (nonatomic, strong) NSNumber * fromMe; + +/** + * The 'type' property can be used to inject event messages. + * For example: "JohnDoe entered the room". + * + * You can define your own types to suit your needs. + * All normal messages will have a type of zero. +**/ +@property (nonatomic, strong) NSNumber * type; + +/** + * If a single instance of XMPPRoomCoreDataStorage is shared between multiple xmppStream's, + * this may be needed to distinguish between the streams. +**/ +@property (nonatomic, strong) NSString *streamBareJidStr; + +@end diff --git a/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightMessageCoreDataStorageObject.m b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightMessageCoreDataStorageObject.m new file mode 100644 index 0000000000..a84b75230c --- /dev/null +++ b/Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightMessageCoreDataStorageObject.m @@ -0,0 +1,205 @@ +#import "XMPPRoomLightMessageCoreDataStorageObject.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + + +@interface XMPPRoomLightMessageCoreDataStorageObject () + +@property(nonatomic,strong) XMPPJID * primitiveRoomJID; +@property(nonatomic,strong) NSString * primitiveRoomJIDStr; + +@property(nonatomic,strong) XMPPJID * primitiveJid; +@property(nonatomic,strong) NSString * primitiveJidStr; + +@property(nonatomic,strong) XMPPMessage * primitiveMessage; +@property(nonatomic,strong) NSString * primitiveMessageStr; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation XMPPRoomLightMessageCoreDataStorageObject + +@dynamic message; +@dynamic messageStr; + +@dynamic roomJID, primitiveRoomJID; +@dynamic roomJIDStr, primitiveRoomJIDStr; + +@dynamic jid, primitiveJid; +@dynamic jidStr, primitiveJidStr; + +@dynamic nickname; +@dynamic body; +@dynamic localTimestamp; +@dynamic remoteTimestamp; +@dynamic isFromMe; +@dynamic fromMe; + +@dynamic type; + +@dynamic streamBareJidStr; + +@dynamic primitiveMessage; +@dynamic primitiveMessageStr; + +#pragma mark Transient roomJID + +- (XMPPJID *)roomJID +{ + // Create and cache on demand + + [self willAccessValueForKey:@"roomJID"]; + XMPPJID *tmp = self.primitiveRoomJID; + [self didAccessValueForKey:@"roomJID"]; + + if (tmp == nil) + { + NSString *roomJIDStr = self.roomJIDStr; + if (roomJIDStr) + { + tmp = [XMPPJID jidWithString:roomJIDStr]; + self.primitiveRoomJID = tmp; + } + } + + return tmp; +} + +- (void)setRoomJID:(XMPPJID *)roomJID +{ + [self willChangeValueForKey:@"roomJID"]; + [self willChangeValueForKey:@"roomJIDStr"]; + + self.primitiveRoomJID = roomJID; + self.primitiveRoomJIDStr = [roomJID full]; + + [self didChangeValueForKey:@"roomJID"]; + [self didChangeValueForKey:@"roomJIDStr"]; +} + +- (void)setRoomJIDStr:(NSString *)roomJIDStr +{ + [self willChangeValueForKey:@"roomJID"]; + [self willChangeValueForKey:@"roomJIDStr"]; + + self.primitiveRoomJID = [XMPPJID jidWithString:roomJIDStr]; + self.primitiveRoomJIDStr = roomJIDStr; + + [self didChangeValueForKey:@"roomJID"]; + [self didChangeValueForKey:@"roomJIDStr"]; +} + +#pragma mark Transient jid + +- (XMPPJID *)jid +{ + // Create and cache on demand + + [self willAccessValueForKey:@"jid"]; + XMPPJID *tmp = self.primitiveJid; + [self didAccessValueForKey:@"jid"]; + + if (tmp == nil) + { + NSString *jidStr = self.jidStr; + if (jidStr) + { + tmp = [XMPPJID jidWithString:jidStr]; + self.primitiveJid = tmp; + } + } + + return tmp; +} + +- (void)setJid:(XMPPJID *)jid +{ + [self willChangeValueForKey:@"jid"]; + [self willChangeValueForKey:@"jidStr"]; + + self.primitiveJid = jid; + self.primitiveJidStr = [jid full]; + + [self didChangeValueForKey:@"jid"]; + [self didChangeValueForKey:@"jidStr"]; +} + +- (void)setJidStr:(NSString *)jidStr +{ + [self willChangeValueForKey:@"jid"]; + [self willChangeValueForKey:@"jidStr"]; + + self.primitiveJid = [XMPPJID jidWithString:jidStr]; + self.primitiveJidStr = jidStr; + + [self didChangeValueForKey:@"jid"]; + [self didChangeValueForKey:@"jidStr"]; +} + +#pragma mark Scalar + +- (BOOL)isFromMe +{ + return [[self fromMe] boolValue]; +} + +- (void)setIsFromMe:(BOOL)value +{ + self.fromMe = @(value); +} + +#pragma mark - Message +- (XMPPMessage *)message +{ + // Create and cache on demand + [self willAccessValueForKey:@"message"]; + + XMPPMessage *message = self.primitiveMessage; + + [self didAccessValueForKey:@"message"]; + + if (message == nil) + { + NSString *messageStr = self.messageStr; + + if (messageStr) + { + NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:messageStr error:nil]; + + message = [XMPPMessage messageFromElement:element]; + self.primitiveMessage = message; + } + } + + return message; +} + +- (void)setMessage:(XMPPMessage *)message +{ + [self willChangeValueForKey:@"message"]; + [self willChangeValueForKey:@"messageStr"]; + self.primitiveMessage = message; + self.primitiveMessageStr = [message compactXMLString]; + [self didChangeValueForKey:@"message"]; + [self didChangeValueForKey:@"messageStr"]; +} + +- (void)setMessageStr:(NSString *)messageStr +{ + [self willChangeValueForKey:@"message"]; + [self willChangeValueForKey:@"messageStr"]; + + NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:messageStr error:nil]; + + self.primitiveMessage = [XMPPMessage messageFromElement:element]; + self.primitiveMessageStr = messageStr; + [self didChangeValueForKey:@"message"]; + [self didChangeValueForKey:@"messageStr"]; +} + +@end diff --git a/Extensions/XMPPMUCLight/XMPPMUCLight.h b/Extensions/XMPPMUCLight/XMPPMUCLight.h new file mode 100644 index 0000000000..b2206d6850 --- /dev/null +++ b/Extensions/XMPPMUCLight/XMPPMUCLight.h @@ -0,0 +1,64 @@ +// +// XMPPMUCLight.h +// Mangosta +// +// Created by Andres on 5/30/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPModule.h" +#import "XMPPJID.h" + +/** + * The XMPPMUCLight module, combined with XMPPRoomLight and associated storage classes, + * provides an implementation of XEP-xxxx: Multi-User Chat Light a Proto XEP. + * More info: https://github.com/fenek/xeps/blob/muc_light/inbox/muc-light.xml + * + * The bulk of the code resides in XMPPRoomLight, which handles the xmpp technical details + * such as creating a room, leaving a room, adding users to a room, fetching the member list and + * sending messages + * + * The XMPPMUCLight class provides general (but important) tasks relating to MUCLight: + * - It discovers rooms for a service. + * - It monitors active XMPPRoomLight instances on the xmppStream. + * - It listens for MUCLigh room affiliation changes sent from other users. + * - It allows to block/unblock users/rooms + * - It lists the list of blocked users/rooms + * + * Server suport: + * - MongooseIM 2.0.0+ (https://github.com/esl/MongooseIM/) + * + * MUC Light: It's more suitable for mobile devices, where your connection might + * go up and down often, but you don't want that to affect the fact that you're "in" + * the room. + * + * Hightlights: + * - Lack of presences: there is no need to rejoin every room on reconnection. + * - Room version allows cheap checks whether room member list/configuration has + * changes. To be implemented in XMPPRoom + **/ + +@interface XMPPMUCLight : XMPPModule { + XMPPIDTracker *xmppIDTracker; +} + +- (nonnull NSSet*)rooms; +- (BOOL)discoverRoomsForServiceNamed:(nonnull NSString *)serviceName; +- (BOOL)requestBlockingList:(nonnull NSString *)serviceName; +- (BOOL)performActionOnElements:(nonnull NSArray<__kindof NSXMLElement *> *)elements forServiceNamed:(nonnull NSString *)serviceName; +@end + +@protocol XMPPMUCLightDelegate +@optional + +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender didDiscoverRooms:(nonnull NSArray<__kindof NSXMLElement*>*)rooms forServiceNamed:(nonnull NSString *)serviceName; +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender failedToDiscoverRoomsForServiceNamed:(nonnull NSString *)serviceName withError:(nonnull NSError *)error; +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender changedAffiliation:(nonnull NSString *)affiliation userJID:(nonnull XMPPJID *)userJID roomJID:(nonnull XMPPJID *)roomJID; + +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender didRequestBlockingList:(nonnull NSArray*)items forServiceNamed:(nonnull NSString *)serviceName; +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender failedToRequestBlockingList:(nonnull NSString *)serviceName withError:(nonnull XMPPIQ *)iq; + +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender didPerformAction:(nonnull XMPPIQ *)serviceName; +- (void)xmppMUCLight:(nonnull XMPPMUCLight *)sender failedToPerformAction:(nonnull XMPPIQ *)iq; + +@end diff --git a/Extensions/XMPPMUCLight/XMPPMUCLight.m b/Extensions/XMPPMUCLight/XMPPMUCLight.m new file mode 100644 index 0000000000..4cde2669b0 --- /dev/null +++ b/Extensions/XMPPMUCLight/XMPPMUCLight.m @@ -0,0 +1,308 @@ +// +// XMPPMUCLight.m +// Mangosta +// +// Created by Andres on 5/30/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPMUC.h" +#import "XMPPFramework.h" +#import "XMPPLogging.h" +#import "XMPPIDTracker.h" +#import "XMPPMUCLight.h" +#import "XMPPRoomLight.h" + +NSString *const XMPPMUCLightDiscoItemsNamespace = @"http://jabber.org/protocol/disco#items"; +NSString *const XMPPRoomLightAffiliations = @"urn:xmpp:muclight:0#affiliations"; +NSString *const XMPPMUCLightErrorDomain = @"XMPPMUCErrorDomain"; +NSString *const XMPPMUCLightBlocking = @"urn:xmpp:muclight:0#blocking"; + +@interface XMPPMUCLight() { + NSMutableSet *rooms; +} +@end + +@implementation XMPPMUCLight + +- (instancetype)init { + return [self initWithDispatchQueue:nil]; +} + +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue { + if ((self = [super initWithDispatchQueue:queue])) { + rooms = [[NSMutableSet alloc] init]; + } + return self; +} + + +- (BOOL)activate:(XMPPStream *)aXmppStream { + if ([super activate:aXmppStream]) { + xmppIDTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue]; + return YES; + } + + return NO; +} + +- (void)deactivate { + dispatch_block_t block = ^{ @autoreleasepool { + [self->xmppIDTracker removeAllIDs]; + self->xmppIDTracker = nil; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + [super deactivate]; +} + +- (nonnull NSSet *)rooms{ + @synchronized(rooms) { + return [rooms copy]; + } +} + +- (BOOL)discoverRoomsForServiceNamed:(nonnull NSString *)serviceName { + + if (serviceName.length < 2) + return NO; + + dispatch_block_t block = ^{ @autoreleasepool { + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPMUCLightDiscoItemsNamespace]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" + to:[XMPPJID jidWithString:serviceName] + elementID:[self->xmppStream generateUUID] + child:query]; + + [self->xmppIDTracker addElement:iq + target:self + selector:@selector(handleDiscoverRoomsQueryIQ:withInfo:) + timeout:60]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); + + return YES; +} + +- (void)handleDiscoverRoomsQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info { + dispatch_block_t block = ^{ @autoreleasepool { + NSXMLElement *errorElem = [iq elementForName:@"error"]; + NSString *serviceName = [iq attributeStringValueForName:@"from" withDefaultValue:@""]; + + if (errorElem) { + NSString *errMsg = [errorElem.children componentsJoinedByString:@", "]; + NSInteger errorCode = [errorElem attributeIntegerValueForName:@"code" withDefaultValue:0]; + NSDictionary *dict = @{NSLocalizedDescriptionKey : errMsg}; + NSError *error = [NSError errorWithDomain:XMPPMUCLightErrorDomain + code:errorCode + userInfo:dict]; + + [self->multicastDelegate xmppMUCLight:self failedToDiscoverRoomsForServiceNamed:serviceName withError:error]; + return; + } + + NSXMLElement *query = [iq elementForName:@"query" + xmlns:XMPPMUCLightDiscoItemsNamespace]; + + NSArray *items = [query elementsForName:@"item"]; + + [self->multicastDelegate xmppMUCLight:self didDiscoverRooms:items forServiceNamed:serviceName]; + + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (BOOL)requestBlockingList:(nonnull NSString *)serviceName{ + if (serviceName.length < 2) + return NO; + + // + // + // + + dispatch_block_t block = ^{ @autoreleasepool { + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPMUCLightBlocking]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" + to:[XMPPJID jidWithString:serviceName] + elementID:[self->xmppStream generateUUID] + child:query]; + + [self->xmppIDTracker addElement:iq + target:self + selector:@selector(handleRequestBlockingList:withInfo:) + timeout:60]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); + + return YES; +} + +- (void)handleRequestBlockingList:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info { + NSString *serviceName = [iq attributeStringValueForName:@"from" withDefaultValue:@""]; + if ([[iq type] isEqualToString:@"result"]) { + NSXMLElement *query = [iq elementForName:@"query"]; + NSArray *children = [query children]; + if (!children) { children = @[]; } + [multicastDelegate xmppMUCLight:self didRequestBlockingList:children forServiceNamed:serviceName]; + }else{ + [multicastDelegate xmppMUCLight:self failedToRequestBlockingList:serviceName withError:iq]; + } +} + +- (BOOL)performActionOnElements:(nonnull NSArray *)elements forServiceNamed:(nonnull NSString *)serviceName{ + if (serviceName.length < 2) + return NO; + + // + // + // hag66@shakespeare.lit + // hag77@shakespeare.lit + // + // + + dispatch_block_t block = ^{ @autoreleasepool { + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" + xmlns:XMPPMUCLightBlocking]; + for (NSXMLElement *element in elements) { + [query addChild:element]; + } + + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" + to:[XMPPJID jidWithString:serviceName] + elementID:[self->xmppStream generateUUID] + child:query]; + + [self->xmppIDTracker addElement:iq + target:self + selector:@selector(handlePerformAction:withInfo:) + timeout:60]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); + return YES; +} + +- (void)handlePerformAction:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info { + //NSString *serviceName = [iq attributeStringValueForName:@"from" withDefaultValue:@""]; + if ([[iq type] isEqualToString:@"result"]) { + //NSXMLElement *query = [iq elementForName:@"query"]; + [multicastDelegate xmppMUCLight:self didPerformAction:iq]; + }else{ + [multicastDelegate xmppMUCLight:self failedToPerformAction:iq]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark XMPPStream Delegate +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message { + + // + // + // aaaaaaa + // user2@shakespeare.lit + // + // + // + + XMPPJID *from = message.from; + NSXMLElement *x = [message elementForName:@"x" xmlns:XMPPRoomLightAffiliations]; + for (NSXMLElement *user in [x elementsForName:@"user"]) { + NSString *affiliation = [user attributeForName:@"affiliation"].stringValue; + XMPPJID *userJID = [XMPPJID jidWithString:user.stringValue]; + [multicastDelegate xmppMUCLight:self changedAffiliation:affiliation userJID:userJID roomJID:from]; + } +} + +- (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module { + + dispatch_block_t block = ^{ @autoreleasepool { + if ([module isKindOfClass:[XMPPRoomLight class]]){ + + XMPPJID *roomJID = [(XMPPRoomLight *)module roomJID]; + + [self->rooms addObject:roomJID]; + } + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module { + dispatch_block_t block = ^{ @autoreleasepool { + if ([module isKindOfClass:[XMPPRoomLight class]]){ + + XMPPJID *roomJID = [(XMPPRoomLight *)module roomJID]; + + // It's common for the room to get deactivated and deallocated before + // we've received the goodbye presence from the server. + // So we're going to postpone for a bit removing the roomJID from the list. + // This way the isMUCRoomElement will still remain accurate + // for presence elements that may arrive momentarily. + + double delayInSeconds = [self delayInSeconds]; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); + dispatch_after(popTime, self->moduleQueue, ^{ @autoreleasepool { + [self->rooms removeObject:roomJID]; + }}); + } + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { + NSString *type = [iq type]; + + if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]) { + return [xmppIDTracker invokeForID:[iq elementID] withObject:iq]; + } + + return NO; +} + +- (double) delayInSeconds { + return 30.0; +} + +@end diff --git a/Extensions/XMPPMUCLight/XMPPRoomLight.h b/Extensions/XMPPMUCLight/XMPPRoomLight.h new file mode 100644 index 0000000000..bc763b9150 --- /dev/null +++ b/Extensions/XMPPMUCLight/XMPPRoomLight.h @@ -0,0 +1,88 @@ +// +// XMPPRoomLight.h +// Mangosta +// +// Created by Andres Canal on 5/27/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPP.h" +#import "XMPPIDTracker.h" +#import "XMPPJID.h" + +@protocol XMPPRoomLightStorage; + +@interface XMPPRoomLight : XMPPModule { + + __strong id xmppRoomLightStorage; + XMPPIDTracker *responseTracker; + +} + +@property (readonly, nonatomic, strong, nonnull) XMPPJID *roomJID; +@property (readonly, nonatomic, strong, nonnull) NSString *domain; +@property (nonatomic, assign) BOOL shouldStoreAffiliationChangeMessages; +@property (assign) BOOL shouldHandleMemberMessagesWithoutBody; + +- (nonnull NSString *)roomname; +- (nonnull NSString *)subject; +- (nonnull NSArray *)knownMembersList; + +- (nonnull instancetype)initWithJID:(nonnull XMPPJID *)roomJID roomname:(nonnull NSString *) roomname; +- (nonnull instancetype)initWithRoomLightStorage:(nullable id )storage jid:(nonnull XMPPJID *)aRoomJID roomname:(nonnull NSString *)aRoomname dispatchQueue:(nullable dispatch_queue_t)queue; +- (void)createRoomLightWithMembersJID:(nullable NSArray *) members; +- (void)leaveRoomLight; +- (void)addUsers:(nonnull NSArray *)users; +- (void)fetchMembersList; +- (void)sendMessage:(nonnull XMPPMessage *)message; +- (void)sendMessageWithBody:(nonnull NSString *)messageBody; +- (void)changeRoomSubject:(nonnull NSString *)roomSubject; +- (void)destroyRoom; +- (void)changeAffiliations:(nonnull NSArray *)members; +- (void)getConfiguration; +- (void)setConfiguration:(nonnull NSArray *)configs; +- (void)flushVersion; +@end + +@protocol XMPPRoomLightStorage +@required + +- (void)handleIncomingMessage:(nonnull XMPPMessage *)message room:(nonnull XMPPRoomLight *)room; +- (void)handleOutgoingMessage:(nonnull XMPPMessage *)message room:(nonnull XMPPRoomLight *)room; + +@end + +@protocol XMPPRoomLightDelegate +@optional + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didReceiveMessage:(nonnull XMPPMessage *)message; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didCreateRoomLight:(nonnull XMPPIQ *)iq; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToCreateRoomLight:(nonnull XMPPIQ *)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didLeaveRoomLight:(nonnull XMPPIQ *)iq; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToLeaveRoomLight:(nonnull XMPPIQ *)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didAddUsers:(nonnull XMPPIQ*) iqResult; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToAddUsers:(nonnull XMPPIQ*)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFetchMembersList:(nonnull XMPPIQ *)iqResult; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToFetchMembersList:(nonnull XMPPIQ *)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didDestroyRoomLight:(nonnull XMPPIQ*) iqResult; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToDestroyRoomLight:(nonnull XMPPIQ*)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didChangeAffiliations:(nonnull XMPPIQ*) iqResult; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToChangeAffiliations:(nonnull XMPPIQ*)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didGetConfiguration:(nonnull XMPPIQ*) iqResult; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToGetConfiguration:(nonnull XMPPIQ*)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didSetConfiguration:(nonnull XMPPIQ*) iqResult; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender didFailToSetConfiguration:(nonnull XMPPIQ*)iq; + +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender roomDestroyed:(nonnull XMPPMessage *)message; +- (void)xmppRoomLight:(nonnull XMPPRoomLight *)sender configurationChanged:(nonnull XMPPMessage *)message; + +@end + diff --git a/Extensions/XMPPMUCLight/XMPPRoomLight.m b/Extensions/XMPPMUCLight/XMPPRoomLight.m new file mode 100644 index 0000000000..2bed21a9e8 --- /dev/null +++ b/Extensions/XMPPMUCLight/XMPPRoomLight.m @@ -0,0 +1,736 @@ +// +// XMPPRoomLight.m +// Mangosta +// +// Created by Andres Canal on 5/27/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPMessage+XEP0045.h" +#import "XMPPRoomLight.h" + +static NSString *const XMPPRoomLightAffiliations = @"urn:xmpp:muclight:0#affiliations"; +static NSString *const XMPPRoomLightConfiguration = @"urn:xmpp:muclight:0#configuration"; +static NSString *const XMPPRoomLightDestroy = @"urn:xmpp:muclight:0#destroy"; + +@interface XMPPRoomLight() { + BOOL shouldStoreAffiliationChangeMessages; + BOOL shouldHandleMemberMessagesWithoutBody; + NSString *roomname; + NSString *subject; + NSArray *knownMembersList; + NSString *configVersion; + NSString *memberListVersion; +} +@end + +@implementation XMPPRoomLight + +- (instancetype)init{ + NSAssert(NO, @"Cannot be instantiated with init!"); + return nil; +} + +- (nonnull instancetype)initWithJID:(nonnull XMPPJID *)roomJID roomname:(nonnull NSString *)_roomname{ + return [self initWithRoomLightStorage:nil jid:roomJID roomname:_roomname dispatchQueue:nil]; +} + +- (nonnull instancetype)initWithRoomLightStorage:(nullable id )storage jid:(nonnull XMPPJID *)aRoomJID roomname:(nonnull NSString *)aRoomname dispatchQueue:(nullable dispatch_queue_t)queue{ + + NSParameterAssert(aRoomJID != nil); + + if ((self = [super initWithDispatchQueue:queue])){ + xmppRoomLightStorage = storage; + _domain = aRoomJID.domain; + _roomJID = aRoomJID; + roomname = aRoomname; + knownMembersList = @[]; + configVersion = @""; + memberListVersion = @""; + } + return self; + +} + +- (BOOL)activate:(XMPPStream *)aXmppStream +{ + if ([super activate:aXmppStream]) + { + responseTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue]; + + return YES; + } + + return NO; +} + +- (void)deactivate +{ + dispatch_block_t block = ^{ @autoreleasepool { + [self->responseTracker removeAllIDs]; + self->responseTracker = nil; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + [super deactivate]; +} + +- (BOOL)shouldStoreAffiliationChangeMessages +{ + __block BOOL result; + dispatch_block_t block = ^{ @autoreleasepool { + result = self->shouldStoreAffiliationChangeMessages; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + return result; +} + +- (void)setShouldStoreAffiliationChangeMessages:(BOOL)newValue +{ + dispatch_block_t block = ^{ @autoreleasepool { + self->shouldStoreAffiliationChangeMessages = newValue; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (BOOL)shouldHandleMemberMessagesWithoutBody +{ + __block BOOL result; + dispatch_block_t block = ^{ @autoreleasepool { + result = self->shouldHandleMemberMessagesWithoutBody; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + return result; +} + +- (void)setShouldHandleMemberMessagesWithoutBody:(BOOL)newValue +{ + dispatch_block_t block = ^{ @autoreleasepool { + self->shouldHandleMemberMessagesWithoutBody = newValue; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (nonnull NSString *)roomname { + @synchronized(roomname) { + return [roomname copy]; + } +} + +- (nonnull NSString *)subject { + @synchronized(subject) { + return [subject copy]; + } +} + +- (NSArray *)knownMembersList { + __block NSArray *result; + dispatch_block_t block = ^{ @autoreleasepool { + result = self->knownMembersList; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + return result; +} + +- (nonnull NSString *)configVersion { + @synchronized(subject) { + return [configVersion copy]; + } +} + +- (nonnull NSString *)memberListVersion { + @synchronized(subject) { + return [memberListVersion copy]; + } +} + +- (void)handleConfigElements:(NSArray *)configElements{ + for (NSXMLElement *element in configElements) { + if([element.name isEqualToString:@"subject"]){ + [self setSubject:element.stringValue]; + } else if([element.name isEqualToString:@"roomname"]) { + [self setRoomname:element.stringValue]; + } + } +} + +- (void)setRoomname:(NSString *)aRoomname{ + dispatch_block_t block = ^{ @autoreleasepool { + self->roomname = aRoomname; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)setSubject:(NSString *)aSubject{ + dispatch_block_t block = ^{ @autoreleasepool { + self->subject = aSubject; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)setKnownMembersList:(NSArray *)aMembersList { + dispatch_block_t block = ^{ @autoreleasepool { + self->knownMembersList = [aMembersList copy]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)setMemberListVersion:(NSString *)aVersion{ + dispatch_block_t block = ^{ @autoreleasepool { + self->memberListVersion = aVersion; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)setConfigVersion:(NSString *)aVersion{ + dispatch_block_t block = ^{ @autoreleasepool { + self->configVersion = aVersion; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)flushVersion{ + [self setConfigVersion:@""]; + [self setMemberListVersion:@""]; +} + +- (void)createRoomLightWithMembersJID:(nullable NSArray *) members{ + + // + // + // + // A Dark Cave + // + // + // user1@shakespeare.lit + // user2@shakespeare.lit + // + // + // + + dispatch_block_t block = ^{ @autoreleasepool { + self->_roomJID = [XMPPJID jidWithUser:[XMPPStream generateUUID] + domain:self.domain + resource:nil]; + + NSString *iqID = [XMPPStream generateUUID]; + NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"]; + [iq addAttributeWithName:@"id" stringValue:iqID]; + [iq addAttributeWithName:@"to" stringValue:self.roomJID.full]; + [iq addAttributeWithName:@"type" stringValue:@"set"]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"urn:xmpp:muclight:0#create"]; + NSXMLElement *configuration = [NSXMLElement elementWithName:@"configuration"]; + [configuration addChild:[NSXMLElement elementWithName:@"roomname" stringValue:self->roomname]]; + + NSXMLElement *occupants = [NSXMLElement elementWithName:@"occupants"]; + for (XMPPJID *jid in members){ + NSXMLElement *userElement = [NSXMLElement elementWithName:@"user" stringValue:jid.bare]; + [userElement addAttributeWithName:@"affiliation" stringValue:@"member"]; + [occupants addChild:userElement]; + } + + [query addChild:configuration]; + [query addChild:occupants]; + + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleCreateRoomLight:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleCreateRoomLight:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]){ + [multicastDelegate xmppRoomLight:self didCreateRoomLight:iq]; + }else{ + [multicastDelegate xmppRoomLight:self didFailToCreateRoomLight:iq]; + } +} + +- (void)leaveRoomLight{ + + // + // + // + // + // + + dispatch_block_t block = ^{ @autoreleasepool { + + NSString *iqID = [XMPPStream generateUUID]; + NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"]; + [iq addAttributeWithName:@"id" stringValue:iqID]; + [iq addAttributeWithName:@"to" stringValue:self.roomJID.full]; + [iq addAttributeWithName:@"type" stringValue:@"set"]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightAffiliations]; + NSXMLElement *user = [NSXMLElement elementWithName:@"user"]; + [user addAttributeWithName:@"affiliation" stringValue:@"none"]; + user.stringValue = self->xmppStream.myJID.bare; + + [query addChild:user]; + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleLeaveRoomLight:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleLeaveRoomLight:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]){ + [multicastDelegate xmppRoomLight:self didLeaveRoomLight:iq]; + }else{ + [multicastDelegate xmppRoomLight:self didFailToLeaveRoomLight:iq]; + } +} + +- (void)addUsers:(nonnull NSArray *)users{ + + // + // + // + // + // + + dispatch_block_t block = ^{ @autoreleasepool { + + NSString *iqID = [XMPPStream generateUUID]; + NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"]; + [iq addAttributeWithName:@"id" stringValue:iqID]; + [iq addAttributeWithName:@"to" stringValue:self.roomJID.full]; + [iq addAttributeWithName:@"type" stringValue:@"set"]; + + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightAffiliations]; + for (XMPPJID *userJID in users) { + NSXMLElement *user = [NSXMLElement elementWithName:@"user"]; + [user addAttributeWithName:@"affiliation" stringValue:@"member"]; + user.stringValue = userJID.full; + + [query addChild:user]; + } + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleAddUsers:withInfo:) + timeout:60.0]; + [self->xmppStream sendElement:iq]; + + }}; + + if (dispatch_get_specific(moduleQueueTag)){ + block(); + }else{ + dispatch_async(moduleQueue, block); + } +} + +- (void)handleAddUsers:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]){ + [multicastDelegate xmppRoomLight:self didAddUsers:iq]; + } else { + [multicastDelegate xmppRoomLight:self didFailToAddUsers:iq]; + } +} + +- (void)fetchMembersList{ + dispatch_block_t block = ^{ @autoreleasepool { + + // + // + // abcdefg + // + // + + NSString *iqID = [XMPPStream generateUUID]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->_roomJID elementID:iqID]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightAffiliations]; + + [query addChild:[NSXMLElement elementWithName:@"version" stringValue:self.memberListVersion]]; + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleFetchMembersListResponse:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleFetchMembersListResponse:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]){ + NSXMLElement *query = [iq elementForName:@"query" + xmlns:XMPPRoomLightAffiliations]; + + NSXMLElement *inVersion = [query elementForName:@"version"]; + if(inVersion){ + [self setMemberListVersion:inVersion.stringValue]; + } + + NSArray *items = [query elementsForName:@"user"]; + if (items) { + [self setKnownMembersList:items]; + } + + [multicastDelegate xmppRoomLight:self didFetchMembersList:iq]; + }else{ + [multicastDelegate xmppRoomLight:self didFailToFetchMembersList:iq]; + } +} + +- (void)destroyRoom { + dispatch_block_t block = ^{ @autoreleasepool { + + // + // + // + + NSString *iqID = [XMPPStream generateUUID]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->_roomJID elementID:iqID]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightDestroy]; + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleDestroyRoom:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleDestroyRoom:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]){ + [multicastDelegate xmppRoomLight:self didDestroyRoomLight:iq]; + } else { + [multicastDelegate xmppRoomLight:self didFailToDestroyRoomLight:iq]; + } +} + +- (void)sendMessage:(nonnull XMPPMessage *)message{ + + dispatch_block_t block = ^{ @autoreleasepool { + + [message addAttributeWithName:@"to" stringValue:[self->_roomJID full]]; + [message addAttributeWithName:@"type" stringValue:@"groupchat"]; + + [self->xmppStream sendElement:message]; + + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)sendMessageWithBody:(nonnull NSString *)messageBody{ + if ([messageBody length] == 0) return; + + NSXMLElement *body = [NSXMLElement elementWithName:@"body" stringValue:messageBody]; + + XMPPMessage *message = [XMPPMessage message]; + [message addChild:body]; + + [self sendMessage:message]; +} + +- (void)changeRoomSubject:(nonnull NSString *)roomSubject{ + + [self setConfiguration:@[[NSXMLElement elementWithName:@"subject" stringValue:roomSubject]]]; + +} + +- (void)changeAffiliations:(nonnull NSArray *)members{ + dispatch_block_t block = ^{ @autoreleasepool { + + // + // + // hag66@shakespeare.lit + // hag77@shakespeare.lit + // hag88@shakespeare.lit + // + // + + NSString *iqID = [XMPPStream generateUUID]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->_roomJID elementID:iqID]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightAffiliations]; + + for (NSXMLElement *element in members){ + [query addChild:element]; + } + + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleChangeAffiliations:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleChangeAffiliations:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]) { + [multicastDelegate xmppRoomLight:self didChangeAffiliations:iq]; + }else{ + [multicastDelegate xmppRoomLight:self didFailToChangeAffiliations:iq]; + } +} + + +- (void)getConfiguration { + dispatch_block_t block = ^{ @autoreleasepool { + + // + // + // abcdefg + // + // + + NSString *iqID = [XMPPStream generateUUID]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self->_roomJID elementID:iqID]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightConfiguration]; + + [query addChild:[NSXMLElement elementWithName:@"version" stringValue:self.configVersion]]; + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleGetConfiguration:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleGetConfiguration:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]) { + + NSXMLElement *query = [[iq elementsForLocalName:@"query" URI:XMPPRoomLightConfiguration] firstObject]; + NSXMLElement *inVersion = [query elementForName:@"version"]; + if(inVersion){ + [self setConfigVersion:inVersion.stringValue]; + } + + NSArray *configElements = [[iq elementsForLocalName:@"query" URI:XMPPRoomLightConfiguration] firstObject].children; + [self handleConfigElements:configElements]; + + [multicastDelegate xmppRoomLight:self didGetConfiguration:iq]; + }else{ + [multicastDelegate xmppRoomLight:self didFailToGetConfiguration:iq]; + } +} + +- (void)setConfiguration:(nonnull NSArray *)configs{ + + dispatch_block_t block = ^{ @autoreleasepool { + + // + // + // A Darker Cave + // + // + + NSString *iqID = [XMPPStream generateUUID]; + XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:self->_roomJID elementID:iqID]; + NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPRoomLightConfiguration]; + + for (NSXMLElement *element in configs){ + [query addChild:element]; + } + + [iq addChild:query]; + + [self->responseTracker addID:iqID + target:self + selector:@selector(handleSetConfiguration:withInfo:) + timeout:60.0]; + + [self->xmppStream sendElement:iq]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)handleSetConfiguration:(XMPPIQ *)iq withInfo:(id )info{ + if ([[iq type] isEqualToString:@"result"]) { + [multicastDelegate xmppRoomLight:self didSetConfiguration:iq]; + }else{ + [multicastDelegate xmppRoomLight:self didFailToSetConfiguration:iq]; + } +} + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq{ + NSString *type = [iq type]; + if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]){ + return [responseTracker invokeForID:[iq elementID] withObject:iq]; + } + + return NO; +} + +- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{ + + XMPPJID *from = [message from]; + + if (![self.roomJID isEqualToJID:from options:XMPPJIDCompareBare]){ + return; // Stanza isn't for our room + } + + // note: do not use [message elementsForName:@"x"] as this will fail to find namespace-qualified elements in Apple's NSXML implementation (DDXML works fine) + BOOL destroyRoom = [message elementsForLocalName:@"x" URI:XMPPRoomLightDestroy].count > 0; + BOOL changeConfiguration = [message elementsForLocalName:@"x" URI:XMPPRoomLightConfiguration].count > 0;; + BOOL changeAffiliantions = [message elementsForLocalName:@"x" URI:XMPPRoomLightAffiliations].count > 0;; + + // Is this a message we need to store (a chat message)? + // + // We store messages that from is full room-id@domain/user-who-sends-message + // and that have something in the body (unless empty messages are allowed) + + if ([from isFull] && [message isGroupChatMessage] && (self.shouldHandleMemberMessagesWithoutBody || [message isMessageWithBody])) { + [xmppRoomLightStorage handleIncomingMessage:message room:self]; + [multicastDelegate xmppRoomLight:self didReceiveMessage:message]; + }else if(destroyRoom){ + [multicastDelegate xmppRoomLight:self roomDestroyed:message]; + }else if(changeConfiguration){ + NSArray *configElements = [message elementForName:@"x"].children; + [self handleConfigElements:configElements]; + + [multicastDelegate xmppRoomLight:self configurationChanged:message]; + } else if (changeAffiliantions && self.shouldStoreAffiliationChangeMessages) { + [xmppRoomLightStorage handleIncomingMessage:message room:self]; + }else{ + // Todo... Handle other types of messages. + } +} + +- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message +{ + XMPPJID *to = [message to]; + + if (![self.roomJID isEqualToJID:to options:XMPPJIDCompareBare]){ + return; // Stanza isn't for our room + } + + // Is this a message we need to store (a chat message)? + // + // A message to all recipients MUST be of type groupchat. + // A message to an individual recipient would have a . + + if ([message isGroupChatMessage] && (self.shouldHandleMemberMessagesWithoutBody || [message isMessageWithBody])) { + [xmppRoomLightStorage handleOutgoingMessage:message room:self]; + } +} + +@end diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000000..d4c261fb9e --- /dev/null +++ b/Package.swift @@ -0,0 +1,180 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "XMPPFramework", + defaultLocalization: "en", + platforms: [ + .macOS(.v10_10), + .iOS(.v9), + .tvOS(.v9) + ], + products: [ + .library( + name: "XMPPFramework", + targets: ["XMPPFramework"] + ), + .library( + name: "XMPPFrameworkSwift", + targets: ["XMPPFrameworkSwift"] + ) + ], + dependencies: [ + .package(name: "CocoaLumberjack", url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", .upToNextMajor(from: "3.6.1")), + .package(name: "CocoaAsyncSocket", url: "https://github.com/robbiehanson/CocoaAsyncSocket.git", .upToNextMajor(from: "7.6.4")), + .package(name: "KissXML", url: "https://github.com/robbiehanson/KissXML.git", .upToNextMajor(from: "5.3.3")), + .package(name: "libidn", url: "https://github.com/chrisballinger/libidn-framework.git", .upToNextMajor(from: "1.35.1")) + ], + targets: [ + .target( + name: "XMPPFramework", + dependencies: [ + "CocoaLumberjack", + "CocoaAsyncSocket", + "KissXML", + "libidn" + ], + path: ".", + exclude: [ + "Swift", + "Xcode", + "README.md", + "copying.txt", + "Cartfile.resolved", + "xmppframework.png", + "Cartfile", + "Core/Info.plist", + "XMPPFramework.podspec" + ], + resources: [ + .process("Extensions/Roster/CoreDataStorage/XMPPRoster.xcdatamodel"), + .process("Extensions/XEP-0045/CoreDataStorage/XMPPRoom.xcdatamodeld"), + .process("Extensions/XEP-0045/HybridStorage/XMPPRoomHybrid.xcdatamodeld"), + .process("Extensions/XEP-0054/CoreDataStorage/XMPPvCard.xcdatamodeld"), + .process("Extensions/XEP-0115/CoreDataStorage/XMPPCapabilities.xcdatamodel"), + .process("Extensions/XEP-0136/CoreDataStorage/XMPPMessageArchiving.xcdatamodeld"), + .process("Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLight.xcdatamodel") + ], + publicHeadersPath: "include/XMPPFramework", + linkerSettings: [ + .linkedLibrary("xml2"), + .linkedLibrary("resolv") + ] + ), + .target( + name: "XMPPFrameworkSwift", + dependencies: [ + "XMPPFramework", + .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack") + ], + path: "Swift", + exclude: [ + "XMPPFrameworkSwift-Info.plist", + "XMPPFrameworkSwift.h" + ] + ), + .target( + name: "XMPPFrameworkTestsShared", + dependencies: [ + "XMPPFramework" + ], + path: "Xcode/Testing-Shared", + exclude: [ + "Info.plist", + "XMPPvCardTests.m", + "XMPPStanzaIdTests.swift", + "OMEMOServerTests.m", + "XMPPDelayedDeliveryTests.m", + "XMPPMessageDeliveryReceiptsTests.m", + "XMPPHTTPFileUploadTests.m", + "XMPPRoomLightTests.m", + "OMEMOModuleTests.m", + "XMPPPushTests.swift", + "EncodeDecodeTest.m", + "XMPPOutOfBandResourceMessagingTests.m", + "XMPPRoomLightCoreDataStorageTests.m", + "XMPPMessageArchiveManagementTests.m", + "CapabilitiesHashingTest.m", + "XMPPMUCLightTests.m", + "OMEMOElementTests.m", + "XMPPURITests.m", + "XMPPSwift.swift", + "XMPPBookmarksTests.swift", + "XMPPOneToOneChatTests.m", + "OMEMOTestStorage.m", + "XMPPManagedMessagingTests.m", + "XMPPStorageHintTests.m", + "XMPPPubSubTests.m" + ], + sources: [ + "XMPPMockStream.m" + ], + publicHeadersPath: "." + ), + .testTarget( + name: "XMPPFrameworkTests", + dependencies: [ + "XMPPFramework", + "XMPPFrameworkTestsShared" + ], + path: "Xcode/Testing-Shared", + exclude: [ + "Info.plist", + "XMPPMockStream.m", + "XMPPBookmarksTests.swift", + "XMPPPushTests.swift", + "XMPPStanzaIdTests.swift", + "XMPPSwift.swift" + ] + ), + .testTarget( + name: "XMPPFrameworkSwiftTests", + dependencies: [ + "XMPPFramework", + "XMPPFrameworkSwift", + "XMPPFrameworkTestsShared" + ], + path: "Xcode", + exclude: [ + "Gemfile", + "Gemfile.lock", + "Examples", + "Testing-Carthage", + "Testing-iOS", + "Testing-macOS", + "Testing-Shared/OMEMOTestStorage.m", + "Testing-Shared/Info.plist", + "Testing-Shared/XMPPManagedMessagingTests.m", + "Testing-Shared/XMPPMessageArchiveManagementTests.m", + "Testing-Shared/XMPPOutOfBandResourceMessagingTests.m", + "Testing-Shared/XMPPRoomLightCoreDataStorageTests.m", + "Testing-Shared/XMPPRoomLightTests.m", + "Testing-Shared/XMPPMessageDeliveryReceiptsTests.m", + "Testing-Shared/OMEMOServerTests.m", + "Testing-Shared/CapabilitiesHashingTest.m", + "Testing-Shared/XMPPOneToOneChatTests.m", + "Testing-Shared/XMPPDelayedDeliveryTests.m", + "Testing-Shared/XMPPMockStream.m", + "Testing-Shared/XMPPPubSubTests.m", + "Testing-Shared/XMPPHTTPFileUploadTests.m", + "Testing-Shared/XMPPvCardTests.m", + "Testing-Shared/OMEMOElementTests.m", + "Testing-Shared/OMEMOModuleTests.m", + "Testing-Shared/EncodeDecodeTest.m", + "Testing-Shared/XMPPStorageHintTests.m", + "Testing-Shared/XMPPURITests.m", + "Testing-Shared/XMPPMUCLightTests.m", + "Testing-Shared/XMPPFrameworkTests-Bridging-Header.h" + ], + sources: [ + "Testing-Shared/XMPPBookmarksTests.swift", + "Testing-Shared/XMPPPushTests.swift", + "Testing-Shared/XMPPStanzaIdTests.swift", + "Testing-Shared/XMPPSwift.swift", + "Testing-Swift/XMPPBookmarksModuleTests.swift", + "Testing-Swift/XMPPPresenceTests.swift", + "Testing-Swift/XMPPvCardTempTests.swift" + ] + ) + ] +) diff --git a/README.markdown b/README.markdown deleted file mode 100644 index 0052f97d45..0000000000 --- a/README.markdown +++ /dev/null @@ -1,18 +0,0 @@ -An XMPP Framework in Objective-C for the Mac / iOS development community. - -XMPPFramework provides a core implementation of RFC-3920 (the xmpp standard), along with the tools needed to read & write XML. It comes with multiple popular extensions (XEP's), all built atop a modular architecture, allowing you to plug-in any code needed for the job. Additionally the framework is massively parallel and thread-safe. Structured using GCD, this framework performs well regardless of whether it's being run on an old iPhone, or on a 12-core Mac Pro. (And it won't block the main thread... at all) - -
-**[Overview of the XMPP Framework](https://github.com/robbiehanson/XMPPFramework/wiki/IntroToFramework)**
-**[Getting started using XMPPFramework on Mac OS X](https://github.com/robbiehanson/XMPPFramework/wiki/GettingStarted_Mac)**
-**[Getting started using XMPPFramework on iOS](https://github.com/robbiehanson/XMPPFramework/wiki/GettingStarted_iOS)**
-**[XEPs supported by the XMPPFramework](https://github.com/robbiehanson/XMPPFramework/wiki/XEPs)**
-**[Learn more about XMPPFramework](https://github.com/robbiehanson/XMPPFramework/wiki)**
- -
-Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/XMPPFramework/wiki) articles? Try the **[mailing list](http://groups.google.com/group/xmppframework)**. -
-
-Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CV6XGZTPQU9HY) - - diff --git a/README.md b/README.md new file mode 100644 index 0000000000..bf92f9d42d --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ + +![XMPPFramework](xmppframework.png) + +## XMPPFramework +[![Build Status](https://travis-ci.org/robbiehanson/XMPPFramework.svg?branch=master)](https://travis-ci.org/robbiehanson/XMPPFramework) [![Version Status](https://img.shields.io/cocoapods/v/XMPPFramework.svg?style=flat)](https://github.com/robbiehanson/XMPPFramework) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](https://img.shields.io/cocoapods/p/XMPPFramework.svg?style=flat)](https://cocoapods.org/?q=XMPPFramework) [![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-orange.svg?style=flat)](http://opensource.org/licenses/BSD-3-Clause) + + +An XMPP Framework in Objective-C for the Mac and iOS development community. + +### Abstract +XMPPFramework provides a core implementation of RFC-3920 (the XMPP standard), along with the tools needed to read & write XML. It comes with multiple popular extensions (XEP's), all built atop a modular architecture, allowing you to plug-in any code needed for the job. Additionally the framework is massively parallel and thread-safe. Structured using GCD, this framework performs well regardless of whether it's being run on an old iPhone, or on a 12-core Mac Pro. (And it won't block the main thread... at all) + +### Install + +The minimum deployment target is iOS 8.0 / macOS 10.9 / tvOS 9.0. + +### Migration from 3.7 to 4.0 + +There have been a number of changes to the public API of XMPPFramework in an attempt to improve the ergnomics and safety when used with Swift. Most Objective-C projects should require no changes, with a few minor exceptions. Many (simple) changes will be required for pure Swift projects, mostly due to the new nullability annotations. The process is still not complete so please submit issues and help if possible to minimize future breaking changes. + +* Swift Support in XMPPFrameworkSwift.framework and XMPPFramework/Swift subspec +* Modern Objective-C Syntax: Nullability annotations and generics. +* Most of Core, Authentication, Categories, and Utilities have been audited. Additional help is needed for Extensions. +* XMPPJID `bareJID` is now imported into Swift as `bareJID` instead of `bare` to prevent conflict with `bare` String. Also applies to `domainJID`. +* XMPPPresence `intShow` has been renamed `showValue` and is now an `XMPPPresenceShow` enum instead of `int`. This will be a warning in 4.0 but will be removed in 4.1. +* The XMPPMessage `chatState` string value is now imported into Swift as a native Swift String enum when using the Swift extensions. A new `chatStateValue` property is provided for accessing the raw String value in both Swift and Obj-C. +* Readonly properties are used instead of getter methods where applicable. Getter naming overrides for properties have been removed to reflect Apple's approach. +* The following modules still need an audit. If you use these modules please help out and contribute some time to audit them and submit a pull request, otherwise their API may contain breaking changes in future releases. + + * XEP-0191 Blocking + * XEP-0199 Ping + * XEP-0202 Time + * XEP-0136 Archiving + * XEP-0115 Capabilities (CoreDataStorage unaudited) + * XEP-0045 MUC (Storage unaudited) + * XEP-0054 vCardTemp (CoreDataStorage unaudited) + * XEP-0016 Privacy + * XEP-0012 Last Activity + * XEP-0009 RPC + * Roster (Storage unaudited) + * XMPPGoogleSharedStatus + * FileTransfer + * CoreDataStorage + * BandwidthMonitor + +### Swift Support + +XMPPFramework is now accepting contributions written in Swift, with some limitations. Swift code must be isolated in the `Swift/` folder, and none of the existing or future Obj-C code may depend upon it. All public APIs written in Swift must be Obj-C compatible and marked with `@objc`. + +See the Contributing section below for more details. + +#### CocoaPods + +The easiest way to install XMPPFramework is using CocoaPods. + +To install only the Objective-C portion of the framework: + +```ruby +pod 'XMPPFramework' +``` + +To use the new Swift additions: + + +``` +use_frameworks! +pod 'XMPPFramework/Swift' +``` + +After `pod install` open the `.xcworkspace` and import: + +```objc +@import XMPPFramework; // Objective-C +``` + +```swift +import XMPPFramework // Swift +``` + +#### Carthage + +To integrate XMPPFramework into your Xcode project using Carthage, specify it in your `Cartfile`: + +``` +github "robbiehanson/XMPPFramework" +``` + +Run `carthage` to build the framework and drag the built `XMPPFramework.framework` into your Xcode project. If you'd like to include new features written in Swift, drag `XMPPFrameworkSwift.framework` into your project as well. You'll need to manually `import XMPPFrameworkSwift` in your headers. + +### Contributing + +Pull requests are welcome! If you are planning a larger feature, please open an issue first for community input. Please use modern Objective-C syntax, including nullability annotations and generics. Here's some tips to make the process go more smoothly: + +* Make sure to add any new files to the iOS, macOS, and tvOS targets for `XMPPFramework.framework` in `XMPPFramework.xcodeproj`, and ensure any applicable header files are set to public. +* Please try to write your code in a way that's testable. Using `XMPPMockStream` makes testing pretty easy. Look at examples in `Testing-Shared` for inspiration. +* You will need both CocoaPods and Carthage to work on tests. Run `carthage checkout` in the root of the repository, and `bundle install && bundle exec pod install` in the `Testing-iOS` and `Testing-macOS` folders. +* Create your test files to the `Testing-Shared` folder, and then add them to the iOS, macOS, and tvOS targets in `Testing-Carthage/XMPPFrameworkTests.xcodeproj`, `Testing-macOS/XMPPFrameworkTests.xcworkspace` and `Testing-iOS/XMPPFrameworkTests.xcworkspace`. +* If you plan on writing Swift code, please keep it isolated in the `Swift/` folder, and ensure none of the pure Obj-C code has dependencies on it. All public APIs must be Obj-C compatible and marked with `@objc`. Remember to add your files to the `XMPPFrameworkSwift.framework` target. Ensure that all your unit tests pass for both the CocoaPods and Carthage integrations. For an example, look at `Testing-Carthage/XMPPFrameworkSwiftTests.xcodeproj`, `Testing-Swift/SwiftOnlyTest.swift`, and the `XMPPFrameworkSwiftTests` targets within `Testing-macOS` and `Testing-iOS`. + +Looking to help but don't know where to start? + +* A large portion of the framework is not yet annotated for nullability and generics. +* Adding more test coverage is always appreciated +* Modernizing the old Examples projects + +#### Security Issues + +If you find a security problem, please do not open a public issue on GitHub. Instead, email one of the maintainers directly: + +* [chris@chatsecure.org](mailto:chris@chatsecure.org) [`GPG 50F7D255`](https://chatsecure.org/assets/pubkeys/50F7D255.asc) + +### Wiki: +For more info please take a look at the wiki. + +- [Overview of the XMPP Framework](https://github.com/robbiehanson/XMPPFramework/wiki/IntroToFramework) +- [Getting started using XMPPFramework on Mac OS X](https://github.com/robbiehanson/XMPPFramework/wiki/GettingStarted_Mac) +- [Getting started using XMPPFramework on iOS](https://github.com/robbiehanson/XMPPFramework/wiki/GettingStarted_iOS) +- [XEPs supported by the XMPPFramework](https://github.com/robbiehanson/XMPPFramework/wiki/XEPs) +- [Learn more about XMPPFramework](https://github.com/robbiehanson/XMPPFramework/wiki) + + +Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/XMPPFramework/wiki) articles? Try the [mailing list](http://groups.google.com/group/xmppframework). + +### Donation: + +Love the project? Wanna buy me a ☕️? (or a 🍺 😀): + +[![donation-bitcoin](https://bitpay.com/img/donate-sm.png)](https://onename.com/robbiehanson) +[![donation-paypal](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CV6XGZTPQU9HY) + diff --git a/Sample_XMPPFramework.h b/Sample_XMPPFramework.h deleted file mode 100644 index 65bf268f09..0000000000 --- a/Sample_XMPPFramework.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// This file is designed to be customized by YOU. -// -// Copy this file and rename it to "XMPPFramework.h". Then add it to your project. -// As you pick and choose which parts of the framework you need for your application, add them to this header file. -// -// Various modules available within the framework optionally interact with each other. -// E.g. The XMPPPing module utilizes the XMPPCapabilities module to advertise support XEP-0199. -// -// However, the modules can only interact if they're both added to your xcode project. -// E.g. If XMPPCapabilities isn't a part of your xcode project, then XMPPPing shouldn't attempt to reference it. -// -// So how do the individual modules know if other modules are available? -// Via this header file. -// -// If you #import "XMPPCapabilities.h" in this file, then _XMPP_CAPABILITIES_H will be defined for other modules. -// And they can automatically take advantage of it. -// - -// CUSTOMIZE ME ! -// THIS HEADER FILE SHOULD BE TAILORED TO MATCH YOUR APPLICATION. - -// The following is standard: - -#import "XMPP.h" - -// List the modules you're using here: -// (the following may not be a complete list) - -//#import "XMPPBandwidthMonitor.h" -// -//#import "XMPPCoreDataStorage.h" -// -//#import "XMPPReconnect.h" -// -//#import "XMPPRoster.h" -//#import "XMPPRosterMemoryStorage.h" -//#import "XMPPRosterCoreDataStorage.h" -// -//#import "XMPPJabberRPCModule.h" -//#import "XMPPIQ+JabberRPC.h" -//#import "XMPPIQ+JabberRPCResponse.h" -// -//#import "XMPPPrivacy.h" -// -//#import "XMPPMUC.h" -//#import "XMPPRoom.h" -//#import "XMPPRoomMemoryStorage.h" -//#import "XMPPRoomCoreDataStorage.h" -//#import "XMPPRoomHybridStorage.h" -// -//#import "XMPPvCardTempModule.h" -//#import "XMPPvCardCoreDataStorage.h" -// -//#import "XMPPPubSub.h" -// -//#import "TURNSocket.h" -// -//#import "XMPPDateTimeProfiles.h" -//#import "NSDate+XMPPDateTimeProfiles.h" -// -//#import "XMPPMessage+XEP_0085.h" -// -//#import "XMPPTransports.h" -// -//#import "XMPPCapabilities.h" -//#import "XMPPCapabilitiesCoreDataStorage.h" -// -//#import "XMPPvCardAvatarModule.h" -// -//#import "XMPPMessage+XEP_0184.h" -// -//#import "XMPPPing.h" -//#import "XMPPAutoPing.h" -// -//#import "XMPPTime.h" -//#import "XMPPAutoTime.h" -// -//#import "XMPPElement+Delay.h" diff --git a/Swift/Core/XMLElement.swift b/Swift/Core/XMLElement.swift new file mode 100644 index 0000000000..159632821b --- /dev/null +++ b/Swift/Core/XMLElement.swift @@ -0,0 +1,28 @@ +// +// XMLElement.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/15/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +import KissXML + +/** + * For whatever reason Swift does not reliably import all of the methods from NSXMLElement+XMPP.h, possibly due to the typealias bug between DDXMLElement and XMLElement on iOS. To prevent this issue, we can simply reimplement the missing ones here. + */ +public extension XMLElement { + + // MARK: Extracting a single element. + + func element(forName name: String) -> XMLElement? { + let elements = self.elements(forName: name) + return elements.first + } + + func element(forName name: String, xmlns: String) -> XMLElement? { + let elements = self.elements(forLocalName: name, uri: xmlns) + return elements.first + } +} diff --git a/Swift/Core/XMPPIQ.swift b/Swift/Core/XMPPIQ.swift new file mode 100644 index 0000000000..6f69597c18 --- /dev/null +++ b/Swift/Core/XMPPIQ.swift @@ -0,0 +1,34 @@ +// +// XMPPIQ.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/14/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +public extension XMPPIQ { + enum IQType: String { + case get + case set + case result + case error + } + + var iqType: IQType? { + guard let type = self.type else { return nil } + let iqType = IQType(rawValue: type) + return iqType + } + + convenience init(iqType: IQType, + to JID: XMPPJID? = nil, + elementID eid: String? = nil, + child childElement: XMLElement? = nil) { + self.init(type: iqType.rawValue, to: JID, elementID: eid, child: childElement) + } +} diff --git a/Swift/Core/XMPPMessage.swift b/Swift/Core/XMPPMessage.swift new file mode 100644 index 0000000000..78ce309784 --- /dev/null +++ b/Swift/Core/XMPPMessage.swift @@ -0,0 +1,36 @@ +// +// XMPPMessage.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/21/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +public extension XMPPMessage { + enum MessageType: String { + case chat + case error + case groupchat + case headline + case normal + } + + var messageType: MessageType? { + guard let type = self.type else { + return nil + } + return MessageType(rawValue: type) + } + + convenience init(messageType: MessageType? = nil, + to: XMPPJID? = nil, + elementID: String? = nil, + child: XMLElement? = nil) { + self.init(type: messageType?.rawValue, to: to, elementID: elementID, child: child) + } +} diff --git a/Swift/Core/XMPPModule.swift b/Swift/Core/XMPPModule.swift new file mode 100644 index 0000000000..e6b4a06009 --- /dev/null +++ b/Swift/Core/XMPPModule.swift @@ -0,0 +1,79 @@ +// +// XMPPModule.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/14/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +// MARK: - Multicast Delegate +public extension XMPPModule { + + /** + * Multicast helper which, when used with the invoke function in the class extension, + * helps with code completion of the intended delegate methods. + * + * Important: You must create a stub extension on Multicast conforming to the + * delegate type(s) your module will broadcast. + * + * For example, in a XMPPModule subclass called XMPPBookmarksModule + * with a multicast delegate called XMPPBookmarksDelegate, somewhere + * you will need to create an empty class extension like this: + * + * extension GCDMulticastDelegate: XMPPBookmarksDelegate {} + * + * Then in your code you may safely call: + * + * multicast.invoke(ofType: XMPPBookmarksDelegate.self, { (multicast) in + * multicast.xmppBookmarks!(self, didNotRetrieveBookmarks: obj as? XMPPIQ) + * }) + */ + var multicast: GCDMulticastDelegate { + return __multicastDelegate as! GCDMulticastDelegate + } + + /** + * This helper helps smooth things over with the multicastDelegate. + * Normally you'd have to repeatedly downcast 'Any' to 'AnyObject' every time + * you want to send an arbitrary invocation to the multicastDelegate. + * + * Note: You must use '!' instead of '?' in your method call + * otherwise the invocation will be ignored. + * + * For example, in your XMPPModule subclass: + * + * multicastDelegate.xmppBookmarks!(self, didRetrieve: bookmarks, responseIq: responseIq) + * + */ + var multicastDelegate: AnyObject { + return __multicastDelegate as AnyObject + } +} + +// MARK: - Synchronization +public extension XMPPModule { + + /** + * Dispatches block synchronously or asynchronously on moduleQueue, or + * executes directly if we're already on the moduleQueue. + * This is most useful for synchronizing external read + * access to properties when writing XMPPModule subclasses. + * + * if (dispatch_get_specific(moduleQueueTag)) + * block(); + * else + * dispatch_sync(moduleQueue, block); (or dispatch_async) + */ + func performBlock(async: Bool = false, _ block: @escaping ()->()) { + if async { + __performBlockAsync(block) + } else { + __perform(block) + } + } +} diff --git a/Swift/Core/XMPPPresence.swift b/Swift/Core/XMPPPresence.swift new file mode 100644 index 0000000000..08bdc297f9 --- /dev/null +++ b/Swift/Core/XMPPPresence.swift @@ -0,0 +1,87 @@ +// +// XMPPPresence.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/21/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +public extension XMPPPresence { + /// The 'type' attribute of a presence stanza is OPTIONAL. A presence stanza that does not possess a 'type' attribute is used to signal to the server that the sender is online and available for communication. If included, the 'type' attribute specifies a lack of availability, a request to manage a subscription to another entity's presence, a request for another entity's current presence, or an error related to a previously-sent presence stanza. If included, the 'type' attribute MUST have one of the following values + enum PresenceType: String { + /// Signals that the entity is no longer available for communication. + case unavailable + /// The sender wishes to subscribe to the recipient's presence. + case subscribe + /// The sender has allowed the recipient to receive their presence. + case subscribed + /// The sender is unsubscribing from another entity's presence. + case unsubscribe + /// The subscription request has been denied or a previously-granted subscription has been cancelled. + case unsubscribed + /// A request for an entity's current presence; SHOULD be generated only by a server on behalf of a user. + case probe + /// An error has occurred regarding processing or delivery of a previously-sent presence stanza. + case error + } + + /// The OPTIONAL element contains non-human-readable XML character data that specifies the particular availability status of an entity or specific resource. A presence stanza MUST NOT contain more than one element. The element MUST NOT possess any attributes. If provided, the XML character data value MUST be one of the following (additional availability types could be defined through a properly-namespaced child element of the presence stanza): + enum ShowType: String { + /// The entity or resource is busy (dnd = "Do Not Disturb"). + case dnd + /// The entity or resource is away for an extended period (xa = "eXtended Away"). + case xa + /// The entity or resource is temporarily away. + case away + /// The entity or resource is actively interested in chatting. + case chat + + /// For backwards compatibility with XMPPPresenceShow enum + public var showValue: XMPPPresenceShow { + switch self { + case .dnd: + return .DND + case .xa: + return .XA + case .away: + return .away + case .chat: + return .chat + } + } + } + + var showType: ShowType? { + guard let show = self.show else { + return nil + } + return ShowType(rawValue: show) + } + + var presenceType: PresenceType? { + guard let type = self.type else { + return nil + } + return PresenceType(rawValue: type) + } + + convenience init(type: PresenceType? = nil, + show: ShowType? = nil, + status: String? = nil, + to jid: XMPPJID? = nil) { + self.init(type: type?.rawValue, to: jid) + if let show = show { + let showElement = XMLElement(name: "show", stringValue: show.rawValue) + addChild(showElement) + } + if let status = status { + let statusElement = XMLElement(name: "status", stringValue: status) + addChild(statusElement) + } + } +} diff --git a/Swift/Utilities/GCDMulticastDelegate.swift b/Swift/Utilities/GCDMulticastDelegate.swift new file mode 100644 index 0000000000..a79295d9e1 --- /dev/null +++ b/Swift/Utilities/GCDMulticastDelegate.swift @@ -0,0 +1,42 @@ +// +// GCDMulticastDelegate.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/15/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +/** + * This helper makes it slightly easier to call the MulticastDelegate + * with the caveat that you must create an empty GCDMulticastDelegate class extension + * for the protocols you'd like it to handle. + * + * For example, in a XMPPModule subclass called XMPPBookmarksModule + * with a multicast delegate called XMPPBookmarksDelegate, somewhere + * you will need to create an empty class extension like this: + * + * extension GCDMulticastDelegate: XMPPBookmarksDelegate {} + * + * This will prevent your code from crashing during the forced cast. + */ +public extension GCDMulticastDelegate { + /** + * This is a helper mainly to provide better code completion. + * + * multicast.invoke(ofType: XMPPBookmarksDelegate.self, { (multicast) in + * multicast.xmppBookmarks!(self, didNotRetrieveBookmarks: obj as? XMPPIQ) + * }) + */ + func invoke(ofType: T.Type, _ invocation: (_ multicast: T) -> ()) { + // Crashing here? See the documentation above. + // You must implement a stub extension on GCDMulticastDelegate conforming to the + // delegate type you are attempting to downcast. + invocation(self as! T) + } +} + diff --git a/Swift/XEP-0048/XMPPBookmarksModule.swift b/Swift/XEP-0048/XMPPBookmarksModule.swift new file mode 100644 index 0000000000..67c1132da8 --- /dev/null +++ b/Swift/XEP-0048/XMPPBookmarksModule.swift @@ -0,0 +1,301 @@ +// +// XMPPBookmarksModule.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/14/17. +// Copyright © 2017 Chris Ballinger. All rights reserved. +// + +import Foundation + +#if COCOAPODS + import CocoaLumberjack +#else + import CocoaLumberjackSwift +#endif + +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +@objc public enum XMPPBookmarksMode: Int { + /// Private XML Storage (XEP-0049) + /// https://xmpp.org/extensions/xep-0049.html + case privateXmlStorage + + /// Recommended by spec, but unimplemented + // case pubsub +} + +@objc public protocol XMPPBookmarksDelegate: NSObjectProtocol { + @objc optional func xmppBookmarks(_ sender: XMPPBookmarksModule, didRetrieve bookmarks: [XMPPBookmark], responseIq: XMPPIQ) + @objc optional func xmppBookmarks(_ sender: XMPPBookmarksModule, didNotRetrieveBookmarks errorIq: XMPPIQ?) + + @objc optional func xmppBookmarks(_ sender: XMPPBookmarksModule, didPublish bookmarks: [XMPPBookmark], responseIq: XMPPIQ) + @objc optional func xmppBookmarks(_ sender: XMPPBookmarksModule, didNotPublishBookmarks errorIq: XMPPIQ?) +} + +/// XEP-0048: Booksmarks +/// +/// This specification defines an XML data format for use by XMPP clients in storing bookmarks to mult-user chatrooms and web pages. The chatroom bookmarking function includes the ability to auto-join rooms on login. +/// https://xmpp.org/extensions/xep-0048.html +public class XMPPBookmarksModule: XMPPModule { + + // MARK: - Properties + @objc public let mode: XMPPBookmarksMode + private var tracker: XMPPIDTracker? + + // MARK: - Init + + /// Right now there's only one mode (privateXmlStorage) + @objc public init(mode: XMPPBookmarksMode, + dispatchQueue: DispatchQueue? = nil) { + self.mode = mode + super.init(dispatchQueue: dispatchQueue) + } + + // MARK: - XMPPModule Overrides + @discardableResult override public func activate(_ xmppStream: XMPPStream) -> Bool { + guard super.activate(xmppStream) else { + return false + } + performBlock(async: true) { + self.tracker = XMPPIDTracker(stream: self.xmppStream, dispatchQueue: self.moduleQueue) + } + return true + } + + override public func deactivate() { + performBlock { + self.tracker?.removeAllIDs() + self.tracker = nil + } + super.deactivate() + } + + // MARK: - Public API with multicast response via XMPPBookmarksDelegate + + /// Fetches all of your stored bookmarks on the server and responds via XMPPBookmarksDelegate + @objc public func fetchBookmarks() { + fetchBookmarks({ (_bookmarks, _responseIq) in + guard let bookmarks = _bookmarks, let responseIq = _responseIq else { + self.multicast.invoke(ofType: XMPPBookmarksDelegate.self, { (multicast) in + multicast.xmppBookmarks!(self, didNotRetrieveBookmarks: _responseIq) + }) + return + } + self.multicast.invoke(ofType: XMPPBookmarksDelegate.self, { (multicast) in + multicast.xmppBookmarks!(self, didRetrieve: bookmarks, responseIq: responseIq) + }) + }) + } + + /// Overwrites bookmarks on the server and responds via XMPPBookmarksDelegate + @objc public func publishBookmarks(_ bookmarks: [XMPPBookmark]) { + publishBookmarks(bookmarks, completion: { (success, responseIq) in + if success, let responseIq = responseIq { + self.multicast.invoke(ofType: XMPPBookmarksDelegate.self, { (multicast) in + multicast.xmppBookmarks!(self, didPublish: bookmarks, responseIq: responseIq) + }) + } else { + self.multicast.invoke(ofType: XMPPBookmarksDelegate.self, { (multicast) in + multicast.xmppBookmarks!(self, didNotPublishBookmarks: responseIq) + }) + } + }) + } + + // MARK: - Public API with Block response only + + /// Fetches bookmarks from server. Block response only (will not trigger MulticastDelegate) + @objc public func fetchBookmarks(_ completion: @escaping (_ bookmarks: [XMPPBookmark]?, _ responseIq: XMPPIQ?)->(), completionQueue: DispatchQueue? = nil) { + let storage = XMLElement.storage + let query = XMLElement.query(child: storage) + let get = XMPPIQ(iqType: .get, child: query) + + // Executes completion block on proper queue + let completionHandler = { (_ bookmarks: [XMPPBookmark]?, _ responseIq: XMPPIQ?)->() in + if let completionQueue = completionQueue { + completionQueue.async { + completion(bookmarks, responseIq) + } + } else { + completion(bookmarks, responseIq) + } + } + + // Handles response from XMPPIDTracker + let iqHandler = { (_ obj: Any, _ info: XMPPTrackingInfo) in + guard let responseIq = obj as? XMPPIQ, + let iqType = responseIq.iqType, + let query = responseIq.query, + let storage = query.bookmarksStorageElement, + iqType != .error else { + completionHandler(nil, obj as? XMPPIQ) + return + } + let bookmarks = storage.bookmarks + completionHandler(bookmarks, responseIq) + } + performBlock(async: true) { + self.tracker?.add(get, block: iqHandler, timeout: 30.0) + self.xmppStream?.send(get) + } + } + + /// Fetches and publishes a merged set of bookmarks on the server. The response block will be nil if there was a failure, or the merged set if successful. Block response only (will not trigger MulticastDelegate) + @objc public func fetchAndPublish(bookmarksToAdd: [XMPPBookmark], bookmarksToRemove: [XMPPBookmark]? = nil, completion: @escaping (_ bookmarks: [XMPPBookmark]?, _ responseIq: XMPPIQ?)->(), completionQueue: DispatchQueue? = nil) { + + // Executes completion block on proper queue + let completionHandler = { (_ bookmarks: [XMPPBookmark]?, _ responseIq: XMPPIQ?)->() in + if let completionQueue = completionQueue { + completionQueue.async { + completion(bookmarks, responseIq) + } + } else { + completion(bookmarks, responseIq) + } + } + + fetchBookmarks({ (responseBookmarks, responseIq) in + if let responseBookmarks = responseBookmarks { + let newBookmarks = self.merge(original: responseBookmarks, adding: bookmarksToAdd, removing: bookmarksToRemove ?? []) + self.publishBookmarks(newBookmarks, completion: { (success, responseIq) in + if !success { + completionHandler(nil, responseIq) + } else { + completionHandler(newBookmarks, responseIq) + } + }, completionQueue: completionQueue) + } else { + completionHandler(nil, responseIq) + } + }, completionQueue: completionQueue) + } + + + /// Overwrites bookmarks on the server. Block response only (will not trigger MulticastDelegate) + @objc public func publishBookmarks(_ bookmarks: [XMPPBookmark], completion: @escaping (_ success: Bool, _ responseIq: XMPPIQ?)->(), completionQueue: DispatchQueue? = nil) { + let storage = XMLElement.storage + let query = XMLElement.query(child: storage) + let set = XMPPIQ(iqType: .set, child: query) + + bookmarks.forEach { (bookmark) in + let element = bookmark.element.copy() as! XMLElement + storage.addChild(element) + } + + // Executes completion block on proper queue + let completionHandler = { (_ success: Bool, _ responseIq: XMPPIQ?)->() in + if let completionQueue = completionQueue { + completionQueue.async { + completion(success, responseIq) + } + } else { + completion(success, responseIq) + } + } + + // Handles response from XMPPIDTracker + let iqHandler = { (_ obj: Any, _ info: XMPPTrackingInfo) in + guard let responseIq = obj as? XMPPIQ, + let iqType = responseIq.iqType, + iqType == .result else { + completionHandler(false, obj as? XMPPIQ) + return + } + completionHandler(true, responseIq) + } + performBlock(async: true) { + self.tracker?.add(set, block: iqHandler, timeout: 30.0) + self.xmppStream?.send(set) + } + } + + // MARK: - Private Methods + + /// Merges bookmarks allowing only one unique value of conference.jid or url.url + /// overwriting the contents of original with new values if there is collision + private func merge(original: [XMPPBookmark], adding: [XMPPBookmark], removing: [XMPPBookmark]) -> [XMPPBookmark] { + var bookmarksDict: [String:XMPPBookmark] = [:] + + let keyForBookmark = { (_ bookmark: XMPPBookmark) -> String? in + if let conference = bookmark as? XMPPConferenceBookmark, + let jidString = conference.jid?.full { + return jidString + } else if let url = bookmark as? XMPPURLBookmark, + let urlString = url.url?.absoluteString { + return urlString + } + return nil + } + + let mergeBookmark = { (_ bookmark: XMPPBookmark) in + if let key = keyForBookmark(bookmark) { + bookmarksDict[key] = bookmark + } + } + + original.forEach(mergeBookmark) + adding.forEach(mergeBookmark) + removing.forEach { (bookmark) in + if let key = keyForBookmark(bookmark) { + bookmarksDict.removeValue(forKey: key) + } + } + + let merged = [XMPPBookmark](bookmarksDict.values) + return merged + } +} + +// MARK: - XMPPStreamDelegate + +extension XMPPBookmarksModule: XMPPStreamDelegate { + public func xmppStream(_ sender: XMPPStream, didReceive iq: XMPPIQ) -> Bool { + guard let type = iq.iqType, + type == .result || type == .error else { + return false + } + var success = false + // Some error responses for self or contacts don't have a "from" + if iq.from == nil, let eid = iq.elementID { + success = tracker?.invoke(forID: eid, with: iq) ?? false + } else { + success = tracker?.invoke(for: iq, with: iq) ?? false + } + return success + } +} + +// MARK: - Private Extensions + +/// This is required for the Swift invoke helper forced downcast. +extension GCDMulticastDelegate: XMPPBookmarksDelegate {} + +private extension XMPPIQ { + var query: XMLElement? { + + return element(forName: PrivateXmlStorageConstants.queryElement, xmlns: PrivateXmlStorageConstants.xmlns) + } +} + +private extension XMLElement { + static var storage: XMLElement { + return XMLElement(name: XMPPBookmarkConstants.storageElement, xmlns: XMPPBookmarkConstants.xmlns) + } + + static func query(child: XMLElement? = nil) -> XMLElement { + let query = XMLElement(name: PrivateXmlStorageConstants.queryElement, xmlns: PrivateXmlStorageConstants.xmlns) + if let child = child { + query.addChild(child) + } + return query + } +} + +// MARK: - Constants +fileprivate struct PrivateXmlStorageConstants { + static let xmlns = "jabber:iq:private" + static let queryElement = "query" +} diff --git a/Swift/XEP-0082/Date+XEP_0082.swift b/Swift/XEP-0082/Date+XEP_0082.swift new file mode 100644 index 0000000000..57e13b55d5 --- /dev/null +++ b/Swift/XEP-0082/Date+XEP_0082.swift @@ -0,0 +1,48 @@ +// +// Date+XEP_0082.swift +// XMPPFramework +// +// Created by Chris Ballinger on 12/7/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +public extension Date { + + static func from(xmppDateString: String) -> Date? { + if let date = XMPPDateTimeProfiles.parseDate(xmppDateString) { + return date as Date + } + return nil + } + + static func from(xmppTimeString: String) -> Date? { + if let date = XMPPDateTimeProfiles.parseTime(xmppTimeString) { + return date as Date + } + return nil + } + + static func from(xmppDateTimeString: String) -> Date? { + if let date = XMPPDateTimeProfiles.parseDateTime(xmppDateTimeString) { + return date as Date + } + return nil + } + + var xmppDateString: String { + return (self as NSDate).xmppDateString + } + + var xmppTimeString: String { + return (self as NSDate).xmppTimeString + } + + var xmppDateTimeString: String { + return (self as NSDate).xmppDateTimeString + } +} diff --git a/Swift/XEP-0085/XMPPMessage+XEP_0085.swift b/Swift/XEP-0085/XMPPMessage+XEP_0085.swift new file mode 100644 index 0000000000..1fc22cc74f --- /dev/null +++ b/Swift/XEP-0085/XMPPMessage+XEP_0085.swift @@ -0,0 +1,36 @@ +// +// XMPPMessage+XEP_0085.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/21/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +/// XEP-0085: Chat States +/// https://xmpp.org/extensions/xep-0085.html +public extension XMPPMessage { + enum ChatState: String { + case active + case composing + case paused + case inactive + case gone + } + + var chatState: ChatState? { + guard let chatState = self.chatStateValue else { + return nil + } + return ChatState(rawValue: chatState) + } + + func addChatState(_ chatState: ChatState) { + let element = XMLElement(name: chatState.rawValue, xmlns: ChatStatesXmlns) + addChild(element) + } +} diff --git a/Swift/XEP-0319/XMPPPresence+XEP_0319.swift b/Swift/XEP-0319/XMPPPresence+XEP_0319.swift new file mode 100644 index 0000000000..e825086cad --- /dev/null +++ b/Swift/XEP-0319/XMPPPresence+XEP_0319.swift @@ -0,0 +1,48 @@ +// +// XMPPPresence+XEP_0319.swift +// XMPPFramework +// +// Created by Chris Ballinger on 12/7/17. +// Copyright © 2017 XMPPFramework. All rights reserved. +// + +import Foundation +import KissXML +#if canImport(XMPPFramework) +import XMPPFramework +#endif + +/// XEP-0319: Last User Interaction in Presence +/// This specification defines a way to communicate time of last user interaction with her system using XMPP presence notifications. +/// https://xmpp.org/extensions/xep-0319.html +public extension XMPPPresence { + /// 'urn:xmpp:idle:1' + @objc static let idleXmlns = "urn:xmpp:idle:1" + + @objc var idleSince: Date? { + guard let idleElement = element(forName: "idle", xmlns: XMPPPresence.idleXmlns), + let sinceString = idleElement.attributeStringValue(forName: "since"), + let since = Date.from(xmppDateTimeString: sinceString) else { + return nil + } + return since + } + + @objc func addIdle(since: Date) { + let dateString = since.xmppDateTimeString + let idleElement = XMLElement(name: "idle", xmlns: XMPPPresence.idleXmlns) + idleElement.addAttribute(withName: "since", stringValue: dateString) + addChild(idleElement) + } + + convenience init(type: PresenceType? = nil, + show: ShowType? = nil, + status: String? = nil, + idle since: Date? = nil, + to jid: XMPPJID? = nil) { + self.init(type: type, show: show, status: status, to: jid) + if let since = since { + addIdle(since: since) + } + } +} diff --git a/Swift/XMPPFramework.swift b/Swift/XMPPFramework.swift new file mode 100644 index 0000000000..0d4932f728 --- /dev/null +++ b/Swift/XMPPFramework.swift @@ -0,0 +1,8 @@ +// +// XMPPFramework.swift +// XMPPFramework +// +// Created by Chris Ballinger on 11/12/17. +// + +import Foundation diff --git a/Swift/XMPPFrameworkSwift-Info.plist b/Swift/XMPPFrameworkSwift-Info.plist new file mode 100644 index 0000000000..fbe1e6b314 --- /dev/null +++ b/Swift/XMPPFrameworkSwift-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Swift/XMPPFrameworkSwift.h b/Swift/XMPPFrameworkSwift.h new file mode 100644 index 0000000000..3d2af87504 --- /dev/null +++ b/Swift/XMPPFrameworkSwift.h @@ -0,0 +1,5 @@ +@import Foundation; +@import XMPPFramework; + +FOUNDATION_EXPORT double XMPPFrameworkSwiftVersionNumber; +FOUNDATION_EXPORT const unsigned char XMPPFrameworkSwiftVersionString[]; diff --git a/Utilities/GCDMulticastDelegate.h b/Utilities/GCDMulticastDelegate.h index 81976f6918..3c48a7ee72 100644 --- a/Utilities/GCDMulticastDelegate.h +++ b/Utilities/GCDMulticastDelegate.h @@ -24,15 +24,16 @@ * All delegate dispatching is done asynchronously (which is a critically important architectural design). **/ +NS_ASSUME_NONNULL_BEGIN @interface GCDMulticastDelegate : NSObject - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; -- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; +- (void)removeDelegate:(id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; - (void)removeDelegate:(id)delegate; - (void)removeAllDelegates; -- (NSUInteger)count; +@property (nonatomic, readonly) NSUInteger count; - (NSUInteger)countOfClass:(Class)aClass; - (NSUInteger)countForSelector:(SEL)aSelector; @@ -45,12 +46,13 @@ @interface GCDMulticastDelegateEnumerator : NSObject -- (NSUInteger)count; +@property (nonatomic, readonly) NSUInteger count; - (NSUInteger)countOfClass:(Class)aClass; - (NSUInteger)countForSelector:(SEL)aSelector; -- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr; -- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofClass:(Class)aClass; -- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr forSelector:(SEL)aSelector; +- (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr; +- (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr ofClass:(Class)aClass; +- (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr forSelector:(SEL)aSelector; @end +NS_ASSUME_NONNULL_END diff --git a/Utilities/GCDMulticastDelegate.m b/Utilities/GCDMulticastDelegate.m index 1a3cb95c5c..1785c274d4 100644 --- a/Utilities/GCDMulticastDelegate.m +++ b/Utilities/GCDMulticastDelegate.m @@ -109,7 +109,7 @@ - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueu NSUInteger i; for (i = [delegateNodes count]; i > 0; i--) { - GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:(i-1)]; + GCDMulticastDelegateNode *node = delegateNodes[i - 1]; id nodeDelegate = node.delegate; #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE @@ -580,7 +580,7 @@ - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr { while (currentNodeIndex < numNodes) { - GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex]; + GCDMulticastDelegateNode *node = delegateNodes[currentNodeIndex]; currentNodeIndex++; id nodeDelegate = node.delegate; // snapshot atomic property @@ -605,7 +605,7 @@ - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofC { while (currentNodeIndex < numNodes) { - GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex]; + GCDMulticastDelegateNode *node = delegateNodes[currentNodeIndex]; currentNodeIndex++; id nodeDelegate = node.delegate; // snapshot atomic property @@ -630,7 +630,7 @@ - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr for { while (currentNodeIndex < numNodes) { - GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:currentNodeIndex]; + GCDMulticastDelegateNode *node = delegateNodes[currentNodeIndex]; currentNodeIndex++; id nodeDelegate = node.delegate; // snapshot atomic property diff --git a/Utilities/RFImageToDataTransformer.m b/Utilities/RFImageToDataTransformer.m index a7df71861f..3daeff34e7 100644 --- a/Utilities/RFImageToDataTransformer.m +++ b/Utilities/RFImageToDataTransformer.m @@ -14,7 +14,7 @@ #if TARGET_OS_IPHONE #import #else - +#import #endif diff --git a/Utilities/XMPPIDTracker.h b/Utilities/XMPPIDTracker.h index ea5b58f146..a84066fa50 100644 --- a/Utilities/XMPPIDTracker.h +++ b/Utilities/XMPPIDTracker.h @@ -117,39 +117,36 @@ extern const NSTimeInterval XMPPIDTrackerTimeoutNone; * This class is NOT thread-safe. * It is designed to be used within a thread-safe context (e.g. within a single dispatch_queue). **/ +NS_ASSUME_NONNULL_BEGIN @interface XMPPIDTracker : NSObject -{ - XMPPStream *xmppStream; - dispatch_queue_t queue; - - NSMutableDictionary *dict; -} -- (id)initWithDispatchQueue:(dispatch_queue_t)queue; +- (instancetype)init NS_UNAVAILABLE; -- (id)initWithStream:(XMPPStream *)stream dispatchQueue:(dispatch_queue_t)queue; +- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue; -- (void)addID:(NSString *)elementID target:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout; +- (instancetype)initWithStream:(nullable XMPPStream *)stream dispatchQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; -- (void)addElement:(XMPPElement *)element target:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout; +- (void)addID:(NSString *)elementID target:(nullable id)target selector:(nullable SEL)selector timeout:(NSTimeInterval)timeout; + +- (void)addElement:(XMPPElement *)element target:(nullable id)target selector:(nullable SEL)selector timeout:(NSTimeInterval)timeout; - (void)addID:(NSString *)elementID - block:(void (^)(id obj, id info))block + block:(void (^_Nullable)(id _Nullable obj, id info))block timeout:(NSTimeInterval)timeout; - (void)addElement:(XMPPElement *)element - block:(void (^)(id obj, id info))block + block:(void (^_Nullable)(id _Nullable obj, id info))block timeout:(NSTimeInterval)timeout; - (void)addID:(NSString *)elementID trackingInfo:(id )trackingInfo; - (void)addElement:(XMPPElement *)element trackingInfo:(id )trackingInfo; -- (BOOL)invokeForID:(NSString *)elementID withObject:(id)obj; +- (BOOL)invokeForID:(NSString *)elementID withObject:(nullable id)obj; -- (BOOL)invokeForElement:(XMPPElement *)element withObject:(id)obj; +- (BOOL)invokeForElement:(XMPPElement *)element withObject:(nullable id)obj; -- (NSUInteger)numberOfIDs; +@property (nonatomic, readonly) NSUInteger numberOfIDs; - (void)removeID:(NSString *)elementID; - (void)removeAllIDs; @@ -171,7 +168,7 @@ extern const NSTimeInterval XMPPIDTrackerTimeoutNone; - (void)createTimerWithDispatchQueue:(dispatch_queue_t)queue; - (void)cancelTimer; -- (void)invokeWithObject:(id)obj; +- (void)invokeWithObject:(nullable id)obj; @end @@ -180,21 +177,9 @@ extern const NSTimeInterval XMPPIDTrackerTimeoutNone; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface XMPPBasicTrackingInfo : NSObject -{ - id target; - SEL selector; - - void (^block)(id obj, id info); - - NSTimeInterval timeout; - - NSString *elementID; - XMPPElement *element; - dispatch_source_t timer; -} - -- (id)initWithTarget:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout; -- (id)initWithBlock:(void (^)(id obj, id info))block timeout:(NSTimeInterval)timeout; + +- (instancetype)initWithTarget:(nullable id)target selector:(nullable SEL)selector timeout:(NSTimeInterval)timeout; +- (instancetype)initWithBlock:(void (^_Nullable)(id _Nullable obj, id info))block timeout:(NSTimeInterval)timeout; @property (nonatomic, readonly) NSTimeInterval timeout; @@ -205,6 +190,7 @@ extern const NSTimeInterval XMPPIDTrackerTimeoutNone; - (void)createTimerWithDispatchQueue:(dispatch_queue_t)queue; - (void)cancelTimer; -- (void)invokeWithObject:(id)obj; +- (void)invokeWithObject:(nullable id)obj; @end +NS_ASSUME_NONNULL_END diff --git a/Utilities/XMPPIDTracker.m b/Utilities/XMPPIDTracker.m index b7e8ac6a5d..41b59e04d5 100644 --- a/Utilities/XMPPIDTracker.m +++ b/Utilities/XMPPIDTracker.m @@ -23,13 +23,14 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface XMPPIDTracker () -{ - void *queueTag; -} - +@property (nonatomic) void *queueTag; +@property (nonatomic, strong, nullable) XMPPStream *xmppStream; +@property (nonatomic, strong) dispatch_queue_t queue; +@property (nonatomic, strong) NSMutableDictionary *dict; @end @implementation XMPPIDTracker +@synthesize queueTag, xmppStream, queue, dict; - (id)init { @@ -128,7 +129,7 @@ - (void)addID:(NSString *)elementID trackingInfo:(id )tracking { AssertProperQueue(); - [dict setObject:trackingInfo forKey:elementID]; + dict[elementID] = trackingInfo; [trackingInfo setElementID:elementID]; [trackingInfo createTimerWithDispatchQueue:queue]; @@ -140,7 +141,7 @@ - (void)addElement:(XMPPElement *)element trackingInfo:(id )tr if([[element elementID] length] == 0) return; - [dict setObject:trackingInfo forKey:[element elementID]]; + dict[[element elementID]] = trackingInfo; [trackingInfo setElementID:[element elementID]]; [trackingInfo setElement:element]; @@ -153,7 +154,7 @@ - (BOOL)invokeForID:(NSString *)elementID withObject:(id)obj if([elementID length] == 0) return NO; - id info = [dict objectForKey:elementID]; + id info = dict[elementID]; if (info) { @@ -175,7 +176,7 @@ - (BOOL)invokeForElement:(XMPPElement *)element withObject:(id)obj if ([elementID length] == 0) return NO; - id info = [dict objectForKey:elementID]; + id info = dict[elementID]; if(info) { BOOL valid = YES; @@ -219,7 +220,7 @@ - (void)removeID:(NSString *)elementID { AssertProperQueue(); - id info = [dict objectForKey:elementID]; + id info = dict[elementID]; if (info) { [info cancelTimer]; @@ -244,11 +245,23 @@ - (void)removeAllIDs #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +@interface XMPPBasicTrackingInfo() +@property (nonatomic, strong) id target; +@property (nonatomic) SEL selector; + +@property (nonatomic, strong) void (^block)(id obj, id info); + +@property (nonatomic) NSTimeInterval timeout; + +@property (nonatomic, strong) dispatch_source_t timer; +@end + @implementation XMPPBasicTrackingInfo @synthesize timeout; @synthesize elementID; @synthesize element; +@synthesize target, selector, timer, block; - (id)init { diff --git a/Utilities/XMPPSRVResolver.h b/Utilities/XMPPSRVResolver.h index 3d9ce0d7ea..ebc70d97cb 100644 --- a/Utilities/XMPPSRVResolver.h +++ b/Utilities/XMPPSRVResolver.h @@ -6,39 +6,23 @@ // #import -#import +NS_ASSUME_NONNULL_BEGIN extern NSString *const XMPPSRVResolverErrorDomain; - +@protocol XMPPSRVResolverDelegate; +@class XMPPSRVRecord; @interface XMPPSRVResolver : NSObject -{ - __unsafe_unretained id delegate; - dispatch_queue_t delegateQueue; - - dispatch_queue_t resolverQueue; - void *resolverQueueTag; - - __strong NSString *srvName; - NSTimeInterval timeout; - - BOOL resolveInProgress; - - NSMutableArray *results; - DNSServiceRef sdRef; - - int sdFd; - dispatch_source_t sdReadSource; - dispatch_source_t timeoutTimer; -} /** * The delegate & delegateQueue are mandatory. * The resolverQueue is optional. If NULL, it will automatically create it's own internal queue. **/ -- (id)initWithdDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq resolverQueue:(dispatch_queue_t)rq; +- (instancetype)initWithDelegate:(id)delegate + delegateQueue:(dispatch_queue_t)delegateQueue + resolverQueue:(nullable dispatch_queue_t)resolverQueue; -@property (strong, readonly) NSString *srvName; +@property (strong, readonly, nullable) NSString *srvName; @property (readonly) NSTimeInterval timeout; - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout; @@ -53,10 +37,9 @@ extern NSString *const XMPPSRVResolverErrorDomain; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @protocol XMPPSRVResolverDelegate - -- (void)xmppSRVResolver:(XMPPSRVResolver *)sender didResolveRecords:(NSArray *)records; +@optional +- (void)xmppSRVResolver:(XMPPSRVResolver *)sender didResolveRecords:(NSArray *)records; - (void)xmppSRVResolver:(XMPPSRVResolver *)sender didNotResolveDueToError:(NSError *)error; - @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -74,9 +57,15 @@ extern NSString *const XMPPSRVResolverErrorDomain; NSUInteger srvResultsIndex; } -+ (XMPPSRVRecord *)recordWithPriority:(UInt16)priority weight:(UInt16)weight port:(UInt16)port target:(NSString *)target; ++ (instancetype)recordWithPriority:(UInt16)priority + weight:(UInt16)weight + port:(UInt16)port + target:(NSString *)target; -- (id)initWithPriority:(UInt16)priority weight:(UInt16)weight port:(UInt16)port target:(NSString *)target; +- (instancetype)initWithPriority:(UInt16)priority + weight:(UInt16)weight + port:(UInt16)port + target:(NSString *)target; @property (nonatomic, readonly) UInt16 priority; @property (nonatomic, readonly) UInt16 weight; @@ -84,3 +73,4 @@ extern NSString *const XMPPSRVResolverErrorDomain; @property (nonatomic, readonly) NSString *target; @end +NS_ASSUME_NONNULL_END diff --git a/Utilities/XMPPSRVResolver.m b/Utilities/XMPPSRVResolver.m index 0e9e985b27..e7ebc743d8 100644 --- a/Utilities/XMPPSRVResolver.m +++ b/Utilities/XMPPSRVResolver.m @@ -8,8 +8,13 @@ #import "XMPPSRVResolver.h" #import "XMPPLogging.h" +//#warning Fix "dns.h" issue without resorting to this ugly hack. +// This is a hack to prevent OnionKit's clobbering of the actual system's +//#include "/usr/include/dns.h" + #include #include +#import #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). @@ -41,10 +46,39 @@ - (NSComparisonResult)compareByPriority:(XMPPSRVRecord *)aRecord; #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +@interface XMPPSRVResolver () +{ +#if __has_feature(objc_arc_weak) + __weak id delegate; +#else + __unsafe_unretained id delegate; +#endif + + dispatch_queue_t delegateQueue; + + dispatch_queue_t resolverQueue; + void *resolverQueueTag; + + __strong NSString *srvName; + NSTimeInterval timeout; + + BOOL resolveInProgress; + + NSMutableArray *results; + DNSServiceRef sdRef; + + int sdFd; + dispatch_source_t sdReadSource; + dispatch_source_t timeoutTimer; +} + +@end + @implementation XMPPSRVResolver -- (id)initWithdDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq resolverQueue:(dispatch_queue_t)rq -{ +- (instancetype)initWithDelegate:(id)aDelegate + delegateQueue:(dispatch_queue_t)dq + resolverQueue:(nullable dispatch_queue_t)rq { NSParameterAssert(aDelegate != nil); NSParameterAssert(dq != NULL); @@ -103,7 +137,7 @@ - (NSString *)srvName __block NSString *result = nil; dispatch_block_t block = ^{ - result = [srvName copy]; + result = [self->srvName copy]; }; if (dispatch_get_specific(resolverQueueTag)) @@ -119,7 +153,7 @@ - (NSTimeInterval)timeout __block NSTimeInterval result = 0.0; dispatch_block_t block = ^{ - result = timeout; + result = self->timeout; }; if (dispatch_get_specific(resolverQueueTag)) @@ -168,7 +202,7 @@ - (void)sortResults if (srvResultsCount == 1) { - XMPPSRVRecord *srvRecord = [results objectAtIndex:0]; + XMPPSRVRecord *srvRecord = results[0]; [sortedResults addObject:srvRecord]; [results removeObjectAtIndex:0]; @@ -188,7 +222,7 @@ - (void)sortResults NSUInteger runningSum = 0; NSMutableArray *samePriorityRecords = [NSMutableArray arrayWithCapacity:srvResultsCount]; - XMPPSRVRecord *srvRecord = [results objectAtIndex:0]; + XMPPSRVRecord *srvRecord = results[0]; NSUInteger initialPriority = srvRecord.priority; NSUInteger index = 0; @@ -216,7 +250,7 @@ - (void)sortResults if (++index < srvResultsCount) { - srvRecord = [results objectAtIndex:index]; + srvRecord = results[index]; } else { @@ -262,9 +296,16 @@ - (void)sortResults - (void)succeed { + NSParameterAssert(delegate != nil); + NSParameterAssert(delegateQueue != nil); NSAssert(dispatch_get_specific(resolverQueueTag), @"Invoked on incorrect queue"); XMPPLogTrace(); + + if (!delegate || !delegateQueue) { + XMPPLogError(@"%@: No delegate or queue set for SRV resolver.", THIS_FILE); + return; + } [self sortResults]; @@ -443,7 +484,7 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout { dispatch_block_t block = ^{ @autoreleasepool { - if (resolveInProgress) + if (self->resolveInProgress) { return; } @@ -452,13 +493,13 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout // Save parameters - srvName = [aSRVName copy]; + self->srvName = [aSRVName copy]; - timeout = aTimeout; + self->timeout = aTimeout; // Check parameters - const char *srvNameCStr = [srvName cStringUsingEncoding:NSASCIIStringEncoding]; + const char *srvNameCStr = [self->srvName cStringUsingEncoding:NSASCIIStringEncoding]; if (srvNameCStr == NULL) { [self failWithDNSError:kDNSServiceErr_BadParam]; @@ -469,7 +510,7 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout // Create DNS Service DNSServiceErrorType sdErr; - sdErr = DNSServiceQueryRecord(&sdRef, // Pointer to unitialized DNSServiceRef + sdErr = DNSServiceQueryRecord(&self->sdRef, // Pointer to unitialized DNSServiceRef kDNSServiceFlagsReturnIntermediates, // Flags kDNSServiceInterfaceIndexAny, // Interface index srvNameCStr, // Full domain name @@ -486,17 +527,17 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout // Extract unix socket (so we can poll for events) - sdFd = DNSServiceRefSockFD(sdRef); - if (sdFd < 0) + self->sdFd = DNSServiceRefSockFD(self->sdRef); + if (self->sdFd < 0) { // Todo... } // Create GCD read source for sd file descriptor - sdReadSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, sdFd, 0, resolverQueue); + self->sdReadSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->sdFd, 0, self->resolverQueue); - dispatch_source_set_event_handler(sdReadSource, ^{ @autoreleasepool { + dispatch_source_set_event_handler(self->sdReadSource, ^{ @autoreleasepool { XMPPLogVerbose(@"%@: sdReadSource_eventHandler", THIS_FILE); @@ -505,7 +546,7 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout // Invoking DNSServiceProcessResult will invoke our QueryRecordCallback, // the callback we set when we created the sdRef. - DNSServiceErrorType dnsErr = DNSServiceProcessResult(sdRef); + DNSServiceErrorType dnsErr = DNSServiceProcessResult(self->sdRef); if (dnsErr != kDNSServiceErr_NoError) { [self failWithDNSError:dnsErr]; @@ -516,9 +557,9 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout #if !OS_OBJECT_USE_OBJC dispatch_source_t theSdReadSource = sdReadSource; #endif - DNSServiceRef theSdRef = sdRef; + DNSServiceRef theSdRef = self->sdRef; - dispatch_source_set_cancel_handler(sdReadSource, ^{ @autoreleasepool { + dispatch_source_set_cancel_handler(self->sdReadSource, ^{ @autoreleasepool { XMPPLogVerbose(@"%@: sdReadSource_cancelHandler", THIS_FILE); @@ -529,18 +570,18 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout }}); - dispatch_resume(sdReadSource); + dispatch_resume(self->sdReadSource); // Create timer (if requested timeout > 0) - if (timeout > 0.0) + if (self->timeout > 0.0) { - timeoutTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, resolverQueue); + self->timeoutTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->resolverQueue); - dispatch_source_set_event_handler(timeoutTimer, ^{ @autoreleasepool { + dispatch_source_set_event_handler(self->timeoutTimer, ^{ @autoreleasepool { NSString *errMsg = @"Operation timed out"; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; NSError *err = [NSError errorWithDomain:XMPPSRVResolverErrorDomain code:0 userInfo:userInfo]; @@ -548,13 +589,13 @@ - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout }}); - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (self->timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(timeoutTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(timeoutTimer); + dispatch_source_set_timer(self->timeoutTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(self->timeoutTimer); } - resolveInProgress = YES; + self->resolveInProgress = YES; }}; if (dispatch_get_specific(resolverQueueTag)) @@ -569,39 +610,39 @@ - (void)stop XMPPLogTrace(); - delegate = nil; - if (delegateQueue) + self->delegate = nil; + if (self->delegateQueue) { #if !OS_OBJECT_USE_OBJC dispatch_release(delegateQueue); #endif - delegateQueue = NULL; + self->delegateQueue = NULL; } - [results removeAllObjects]; + [self->results removeAllObjects]; - if (sdReadSource) + if (self->sdReadSource) { // Cancel the readSource. // It will be released from within the cancel handler. - dispatch_source_cancel(sdReadSource); - sdReadSource = NULL; - sdFd = -1; + dispatch_source_cancel(self->sdReadSource); + self->sdReadSource = NULL; + self->sdFd = -1; // The sdRef will be deallocated from within the cancel handler too. - sdRef = NULL; + self->sdRef = NULL; } - if (timeoutTimer) + if (self->timeoutTimer) { - dispatch_source_cancel(timeoutTimer); + dispatch_source_cancel(self->timeoutTimer); #if !OS_OBJECT_USE_OBJC dispatch_release(timeoutTimer); #endif - timeoutTimer = NULL; + self->timeoutTimer = NULL; } - resolveInProgress = NO; + self->resolveInProgress = NO; }}; if (dispatch_get_specific(resolverQueueTag)) @@ -616,6 +657,7 @@ - (void)stop + (NSString *)srvNameFromXMPPDomain:(NSString *)xmppDomain { + NSParameterAssert(xmppDomain != nil); if (xmppDomain == nil) return nil; else diff --git a/Utilities/XMPPStringPrep.h b/Utilities/XMPPStringPrep.h index c1d41a8dc5..2b2d13b80a 100644 --- a/Utilities/XMPPStringPrep.h +++ b/Utilities/XMPPStringPrep.h @@ -1,6 +1,6 @@ #import - +NS_ASSUME_NONNULL_BEGIN @interface XMPPStringPrep : NSObject /** @@ -11,7 +11,7 @@ * * Note: The prep properly converts the string to lowercase, as per the RFC. **/ -+ (NSString *)prepNode:(NSString *)node; ++ (nullable NSString *)prepNode:(NSString *)node; /** * Preps a domain name for use in a JID. @@ -19,7 +19,7 @@ * * See the XMPP RFC (6120) for details. **/ -+ (NSString *)prepDomain:(NSString *)domain; ++ (nullable NSString *)prepDomain:(NSString *)domain; /** * Preps a resource identifier for use in a JID. @@ -27,7 +27,7 @@ * * See the XMPP RFC (6120) for details. **/ -+ (NSString *)prepResource:(NSString *)resource; ++ (nullable NSString *)prepResource:(NSString *)resource; /** * Preps a password with SASLprep profile. @@ -36,6 +36,7 @@ * See the SCRAM RFC (5802) for details. **/ -+ (NSString *) prepPassword:(NSString *)password; ++ (nullable NSString *) prepPassword:(NSString *)password; @end +NS_ASSUME_NONNULL_END diff --git a/Utilities/XMPPStringPrep.m b/Utilities/XMPPStringPrep.m index 5831efd428..ce28adff3f 100644 --- a/Utilities/XMPPStringPrep.m +++ b/Utilities/XMPPStringPrep.m @@ -1,67 +1,31 @@ #import "XMPPStringPrep.h" -#import "stringprep.h" +@import libidn; @implementation XMPPStringPrep + (NSString *)prepNode:(NSString *)node { - if(node == nil) return nil; - - // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. - // We make the buffer just big enough to hold a null-terminated string of this length. - char buf[1024]; - - strncpy(buf, [node UTF8String], sizeof(buf)); - - if(stringprep_xmpp_nodeprep(buf, sizeof(buf)) != 0) return nil; - - return [NSString stringWithUTF8String:buf]; + if (!node) { return nil; } + return [NSString idn_prepNode:node]; } + (NSString *)prepDomain:(NSString *)domain { - if(domain == nil) return nil; - - // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. - // We make the buffer just big enough to hold a null-terminated string of this length. - char buf[1024]; - - strncpy(buf, [domain UTF8String], sizeof(buf)); - - if(stringprep_nameprep(buf, sizeof(buf)) != 0) return nil; - - return [NSString stringWithUTF8String:buf]; + if (!domain) { return nil; } + return [NSString idn_prepDomain:domain]; } + (NSString *)prepResource:(NSString *)resource { - if(resource == nil) return nil; - - // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. - // We make the buffer just big enough to hold a null-terminated string of this length. - char buf[1024]; - - strncpy(buf, [resource UTF8String], sizeof(buf)); - - if(stringprep_xmpp_resourceprep(buf, sizeof(buf)) != 0) return nil; - - return [NSString stringWithUTF8String:buf]; + if (!resource) { return nil; } + return [NSString idn_prepResource:resource]; } + (NSString *)prepPassword:(NSString *)password { - if(password == nil) return nil; - - // Each allowable portion of a JID MUST NOT be more than 1023 bytes in length. - // We make the buffer just big enough to hold a null-terminated string of this length. - char buf[1024]; - - strncpy(buf, [password UTF8String], sizeof(buf)); - - if(stringprep(buf, sizeof(buf), 0, stringprep_saslprep) != 0) return nil; - - return [NSString stringWithUTF8String:buf]; + if (!password) { return nil; } + return [NSString idn_prepPassword:password]; } @end diff --git a/Utilities/XMPPTimer.h b/Utilities/XMPPTimer.h index 64f15c169e..63c5388dc7 100644 --- a/Utilities/XMPPTimer.h +++ b/Utilities/XMPPTimer.h @@ -1,5 +1,6 @@ #import +NS_ASSUME_NONNULL_BEGIN /** * This class is a simple wrapper around dispatch_source_t timers. * @@ -39,3 +40,4 @@ - (void)cancel; @end +NS_ASSUME_NONNULL_END diff --git a/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h b/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h deleted file mode 100644 index a0d1c52bca..0000000000 --- a/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h +++ /dev/null @@ -1,1175 +0,0 @@ -// -// GCDAsyncSocket.h -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q3 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import -#import -#import -#import -#import - -#include // AF_INET, AF_INET6 - -@class GCDAsyncReadPacket; -@class GCDAsyncWritePacket; -@class GCDAsyncSocketPreBuffer; - -extern NSString *const GCDAsyncSocketException; -extern NSString *const GCDAsyncSocketErrorDomain; - -extern NSString *const GCDAsyncSocketQueueName; -extern NSString *const GCDAsyncSocketThreadName; - -extern NSString *const GCDAsyncSocketManuallyEvaluateTrust; -#if TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketUseCFStreamForTLS; -#endif -#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName -#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates -#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer -extern NSString *const GCDAsyncSocketSSLPeerID; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; -extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart; -extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord; -extern NSString *const GCDAsyncSocketSSLCipherSuites; -#if !TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; -#endif - -#define GCDAsyncSocketLoggingContext 65535 - - -enum GCDAsyncSocketError -{ - GCDAsyncSocketNoError = 0, // Never used - GCDAsyncSocketBadConfigError, // Invalid configuration - GCDAsyncSocketBadParamError, // Invalid parameter was passed - GCDAsyncSocketConnectTimeoutError, // A connect operation timed out - GCDAsyncSocketReadTimeoutError, // A read operation timed out - GCDAsyncSocketWriteTimeoutError, // A write operation timed out - GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing - GCDAsyncSocketClosedError, // The remote peer closed the connection - GCDAsyncSocketOtherError, // Description provided in userInfo -}; -typedef enum GCDAsyncSocketError GCDAsyncSocketError; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncSocket : NSObject - -/** - * GCDAsyncSocket uses the standard delegate paradigm, - * but executes all delegate callbacks on a given delegate dispatch queue. - * This allows for maximum concurrency, while at the same time providing easy thread safety. - * - * You MUST set a delegate AND delegate dispatch queue before attempting to - * use the socket, or you will get an error. - * - * The socket queue is optional. - * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue. - * If you choose to provide a socket queue, the socket queue must not be a concurrent queue. - * If you choose to provide a socket queue, and the socket queue has a configured target queue, - * then please see the discussion for the method markSocketQueueTargetQueue. - * - * The delegate queue and socket queue can optionally be the same. -**/ -- (id)init; -- (id)initWithSocketQueue:(dispatch_queue_t)sq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq; - -#pragma mark Configuration - -@property (atomic, weak, readwrite) id delegate; -@property (atomic, strong, readwrite) dispatch_queue_t delegateQueue; - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - -/** - * If you are setting the delegate to nil within the delegate's dealloc method, - * you may need to use the synchronous versions below. -**/ -- (void)synchronouslySetDelegate:(id)delegate; -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - -/** - * By default, both IPv4 and IPv6 are enabled. - * - * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols, - * and can simulataneously accept incoming connections on either protocol. - * - * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol. - * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4. - * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6. - * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen. - * By default, the preferred protocol is IPv4, but may be configured as desired. -**/ - -@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled; -@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled; - -@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6; - -/** - * User data allows you to associate arbitrary information with the socket. - * This data is not used internally by socket in any way. -**/ -@property (atomic, strong, readwrite) id userData; - -#pragma mark Accepting - -/** - * Tells the socket to begin listening and accepting connections on the given port. - * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, - * and the socket:didAcceptNewSocket: delegate method will be invoked. - * - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) -**/ -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * This method is the same as acceptOnPort:error: with the - * additional option of specifying which interface to listen on. - * - * For example, you could specify that the socket should only accept connections over ethernet, - * and not other interfaces such as wifi. - * - * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept connections from the local machine. - * - * You can see the list of interfaces via the command line utility "ifconfig", - * or programmatically via the getifaddrs() function. - * - * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. -**/ -- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; - -#pragma mark Connecting - -/** - * Connects to the given host and port. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: - * and uses the default interface, and no timeout. -**/ -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Connects to the given host and port with an optional timeout. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given host & port, via the optional interface, with an optional timeout. - * - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * The host may also be the special strings "localhost" or "loopback" to specify connecting - * to a service on the local machine. - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - viaInterface:(NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * This method invokes connectToAdd -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * This method is the same as connectToAddress:error: with an additional timeout option. - * To not time out use a negative time interval, or simply use the connectToAddress:error: method. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -/** - * Connects to the given address, using the specified interface and timeout. - * - * The address is specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * The timeout is optional. To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterface:(NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -#pragma mark Disconnecting - -/** - * Disconnects immediately (synchronously). Any pending reads or writes are dropped. - * - * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method - * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods). - * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns. - * - * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method) - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket release]; - * - * If you plan on disconnecting the socket, and then immediately asking it to connect again, - * you'll likely want to do so like this: - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket setDelegate:self]; - * [asyncSocket connect...]; -**/ -- (void)disconnect; - -/** - * Disconnects after all pending reads have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending writes. -**/ -- (void)disconnectAfterReading; - -/** - * Disconnects after all pending writes have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending reads. -**/ -- (void)disconnectAfterWriting; - -/** - * Disconnects after all pending reads and writes have completed. - * After calling this, the read and write methods will do nothing. -**/ -- (void)disconnectAfterReadingAndWriting; - -#pragma mark Diagnostics - -/** - * Returns whether the socket is disconnected or connected. - * - * A disconnected socket may be recycled. - * That is, it can used again for connecting or listening. - * - * If a socket is in the process of connecting, it may be neither disconnected nor connected. -**/ -@property (atomic, readonly) BOOL isDisconnected; -@property (atomic, readonly) BOOL isConnected; - -/** - * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. - * The host will be an IP address. -**/ -@property (atomic, readonly) NSString *connectedHost; -@property (atomic, readonly) uint16_t connectedPort; - -@property (atomic, readonly) NSString *localHost; -@property (atomic, readonly) uint16_t localPort; - -/** - * Returns the local or remote address to which this socket is connected, - * specified as a sockaddr structure wrapped in a NSData object. - * - * @seealso connectedHost - * @seealso connectedPort - * @seealso localHost - * @seealso localPort -**/ -@property (atomic, readonly) NSData *connectedAddress; -@property (atomic, readonly) NSData *localAddress; - -/** - * Returns whether the socket is IPv4 or IPv6. - * An accepting socket may be both. -**/ -@property (atomic, readonly) BOOL isIPv4; -@property (atomic, readonly) BOOL isIPv6; - -/** - * Returns whether or not the socket has been secured via SSL/TLS. - * - * See also the startTLS method. -**/ -@property (atomic, readonly) BOOL isSecure; - -#pragma mark Reading - -// The readData and writeData methods won't block (they are asynchronous). -// -// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue. -// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue. -// -// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) -// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method -// is called to optionally allow you to extend the timeout. -// Upon a timeout, the "socket:didDisconnectWithError:" method is called -// -// The tag is for your convenience. -// You can use it as an array index, step number, state id, pointer, etc. - -/** - * Reads the first available bytes that become available on the socket. - * - * If the timeout value is negative, the read operation will not use a timeout. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, the socket will create a buffer for you. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * A maximum of length bytes will be read. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * If maxLength is zero, no length restriction is enforced. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Reads the given number of bytes. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If the length is 0, this method does nothing and the delegate is not called. -**/ -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the given number of bytes. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * - * If the length is 0, this method does nothing and the delegate is not called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If you pass a maxLength parameter that is less than the length of the data parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * - * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass a maxLength parameter that is less than the length of the data (separator) parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * For performance reasons, the socket will retain it, not copy it. - * So if it is immutable, don't modify it while the socket is using it. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check). - * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr; - -#pragma mark Writing - -/** - * Writes data to the socket, and calls the delegate when finished. - * - * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. - * If the timeout value is negative, the write operation will not use a timeout. - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method - * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed. - * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it. - * This is for performance reasons. Often times, if NSMutableData is passed, it is because - * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time - * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. -**/ -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check). - * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr; - -#pragma mark Security - -/** - * Secures the connection using SSL/TLS. - * - * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes - * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing - * the upgrade to TLS at the same time, without having to wait for the write to finish. - * Any reads or writes scheduled after this method is called will occur over the secured connection. - * - * ==== The available TOP-LEVEL KEYS are: - * - * - GCDAsyncSocketManuallyEvaluateTrust - * The value must be of type NSNumber, encapsulating a BOOL value. - * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer. - * Instead it will pause at the moment evaulation would typically occur, - * and allow us to handle the security evaluation however we see fit. - * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef. - * - * Note that if you set this option, then all other configuration keys are ignored. - * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method. - * - * For more information on trust evaluation see: - * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation - * https://developer.apple.com/library/ios/technotes/tn2232/_index.html - * - * If unspecified, the default value is NO. - * - * - GCDAsyncSocketUseCFStreamForTLS (iOS only) - * The value must be of type NSNumber, encapsulating a BOOL value. - * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption. - * This gives us more control over the security protocol (many more configuration options), - * plus it allows us to optimize things like sys calls and buffer allocation. - * - * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption - * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket - * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property - * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method. - * - * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket, - * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty. - * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings. - * - * If unspecified, the default value is NO. - * - * ==== The available CONFIGURATION KEYS are: - * - * - kCFStreamSSLPeerName - * The value must be of type NSString. - * It should match the name in the X.509 certificate given by the remote party. - * See Apple's documentation for SSLSetPeerDomainName. - * - * - kCFStreamSSLCertificates - * The value must be of type NSArray. - * See Apple's documentation for SSLSetCertificate. - * - * - kCFStreamSSLIsServer - * The value must be of type NSNumber, encapsulationg a BOOL value. - * See Apple's documentation for SSLCreateContext for iOS. - * This is optional for iOS. If not supplied, a NO value is the default. - * This is not needed for Mac OS X, and the value is ignored. - * - * - GCDAsyncSocketSSLPeerID - * The value must be of type NSData. - * You must set this value if you want to use TLS session resumption. - * See Apple's documentation for SSLSetPeerID. - * - * - GCDAsyncSocketSSLProtocolVersionMin - * - GCDAsyncSocketSSLProtocolVersionMax - * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value. - * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax. - * See also the SSLProtocol typedef. - * - * - GCDAsyncSocketSSLSessionOptionFalseStart - * The value must be of type NSNumber, encapsulating a BOOL value. - * See Apple's documentation for kSSLSessionOptionFalseStart. - * - * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord - * The value must be of type NSNumber, encapsulating a BOOL value. - * See Apple's documentation for kSSLSessionOptionSendOneByteRecord. - * - * - GCDAsyncSocketSSLCipherSuites - * The values must be of type NSArray. - * Each item within the array must be a NSNumber, encapsulating - * See Apple's documentation for SSLSetEnabledCiphers. - * See also the SSLCipherSuite typedef. - * - * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only) - * The value must be of type NSData. - * See Apple's documentation for SSLSetDiffieHellmanParams. - * - * ==== The following UNAVAILABLE KEYS are: (with throw an exception) - * - * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsAnyRoot - * - * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsExpiredRoots - * - * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsExpiredCerts - * - * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetEnableCertVerify - * - * - kCFStreamSSLLevel (UNAVAILABLE) - * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead. - * Corresponding deprecated method: SSLSetProtocolVersionEnabled - * - * - * Please refer to Apple's documentation for corresponding SSLFunctions. - * - * If you pass in nil or an empty dictionary, the default settings will be used. - * - * IMPORTANT SECURITY NOTE: - * The default settings will check to make sure the remote party's certificate is signed by a - * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. - * However it will not verify the name on the certificate unless you - * give it a name to verify against via the kCFStreamSSLPeerName key. - * The security implications of this are important to understand. - * Imagine you are attempting to create a secure connection to MySecureServer.com, - * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. - * If you simply use the default settings, and MaliciousServer.com has a valid certificate, - * the default settings will not detect any problems since the certificate is valid. - * To properly secure your connection in this particular scenario you - * should set the kCFStreamSSLPeerName property to "MySecureServer.com". - * - * You can also perform additional validation in socketDidSecure. -**/ -- (void)startTLS:(NSDictionary *)tlsSettings; - -#pragma mark Advanced - -/** - * Traditionally sockets are not closed until the conversation is over. - * However, it is technically possible for the remote enpoint to close its write stream. - * Our socket would then be notified that there is no more data to be read, - * but our socket would still be writeable and the remote endpoint could continue to receive our data. - * - * The argument for this confusing functionality stems from the idea that a client could shut down its - * write stream after sending a request to the server, thus notifying the server there are to be no further requests. - * In practice, however, this technique did little to help server developers. - * - * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close - * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell - * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work. - * Otherwise an error will be occur shortly (when the remote end sends us a RST packet). - * - * In addition to the technical challenges and confusion, many high level socket/stream API's provide - * no support for dealing with the problem. If the read stream is closed, the API immediately declares the - * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does. - * It might sound like poor design at first, but in fact it simplifies development. - * - * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket. - * Thus it actually makes sense to close the socket at this point. - * And in fact this is what most networking developers want and expect to happen. - * However, if you are writing a server that interacts with a plethora of clients, - * you might encounter a client that uses the discouraged technique of shutting down its write stream. - * If this is the case, you can set this property to NO, - * and make use of the socketDidCloseReadStream delegate method. - * - * The default value is YES. -**/ -@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream; - -/** - * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. - * In most cases, the instance creates this queue itself. - * However, to allow for maximum flexibility, the internal queue may be passed in the init method. - * This allows for some advanced options such as controlling socket priority via target queues. - * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. - * - * For example, imagine there are 2 queues: - * dispatch_queue_t socketQueue; - * dispatch_queue_t socketTargetQueue; - * - * If you do this (pseudo-code): - * socketQueue.targetQueue = socketTargetQueue; - * - * Then all socketQueue operations will actually get run on the given socketTargetQueue. - * This is fine and works great in most situations. - * But if you run code directly from within the socketTargetQueue that accesses the socket, - * you could potentially get deadlock. Imagine the following code: - * - * - (BOOL)socketHasSomething - * { - * __block BOOL result = NO; - * dispatch_block_t block = ^{ - * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; - * } - * if (is_executing_on_queue(socketQueue)) - * block(); - * else - * dispatch_sync(socketQueue, block); - * - * return result; - * } - * - * What happens if you call this method from the socketTargetQueue? The result is deadlock. - * This is because the GCD API offers no mechanism to discover a queue's targetQueue. - * Thus we have no idea if our socketQueue is configured with a targetQueue. - * If we had this information, we could easily avoid deadlock. - * But, since these API's are missing or unfeasible, you'll have to explicitly set it. - * - * IF you pass a socketQueue via the init method, - * AND you've configured the passed socketQueue with a targetQueue, - * THEN you should pass the end queue in the target hierarchy. - * - * For example, consider the following queue hierarchy: - * socketQueue -> ipQueue -> moduleQueue - * - * This example demonstrates priority shaping within some server. - * All incoming client connections from the same IP address are executed on the same target queue. - * And all connections for a particular module are executed on the same target queue. - * Thus, the priority of all networking for the entire module can be changed on the fly. - * Additionally, networking traffic from a single IP cannot monopolize the module. - * - * Here's how you would accomplish something like that: - * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock - * { - * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); - * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; - * - * dispatch_set_target_queue(socketQueue, ipQueue); - * dispatch_set_target_queue(iqQueue, moduleQueue); - * - * return socketQueue; - * } - * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket - * { - * [clientConnections addObject:newSocket]; - * [newSocket markSocketQueueTargetQueue:moduleQueue]; - * } - * - * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. - * This is often NOT the case, as such queues are used solely for execution shaping. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; - -/** - * It's not thread-safe to access certain variables from outside the socket's internal queue. - * - * For example, the socket file descriptor. - * File descriptors are simply integers which reference an index in the per-process file table. - * However, when one requests a new file descriptor (by opening a file or socket), - * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. - * So if we're not careful, the following could be possible: - * - * - Thread A invokes a method which returns the socket's file descriptor. - * - The socket is closed via the socket's internal queue on thread B. - * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. - * - Thread A is now accessing/altering the file instead of the socket. - * - * In addition to this, other variables are not actually objects, - * and thus cannot be retained/released or even autoreleased. - * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. - * - * Although there are internal variables that make it difficult to maintain thread-safety, - * it is important to provide access to these variables - * to ensure this class can be used in a wide array of environments. - * This method helps to accomplish this by invoking the current block on the socket's internal queue. - * The methods below can be invoked from within the block to access - * those generally thread-unsafe internal variables in a thread-safe manner. - * The given block will be invoked synchronously on the socket's internal queue. - * - * If you save references to any protected variables and use them outside the block, you do so at your own peril. -**/ -- (void)performBlock:(dispatch_block_t)block; - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's file descriptor(s). - * If the socket is a server socket (is accepting incoming connections), - * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. -**/ -- (int)socketFD; -- (int)socket4FD; -- (int)socket6FD; - -#if TARGET_OS_IPHONE - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's internal CFReadStream/CFWriteStream. - * - * These streams are only used as workarounds for specific iOS shortcomings: - * - * - Apple has decided to keep the SecureTransport framework private is iOS. - * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it. - * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream, - * instead of the preferred and faster and more powerful SecureTransport. - * - * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded, - * Apple only bothers to notify us via the CFStream API. - * The faster and more powerful GCD API isn't notified properly in this case. - * - * See also: (BOOL)enableBackgroundingOnSocket -**/ -- (CFReadStreamRef)readStream; -- (CFWriteStreamRef)writeStream; - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Configures the socket to allow it to operate when the iOS application has been backgrounded. - * In other words, this method creates a read & write stream, and invokes: - * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * - * Returns YES if successful, NO otherwise. - * - * Note: Apple does not officially support backgrounding server sockets. - * That is, if your socket is accepting incoming connections, Apple does not officially support - * allowing iOS applications to accept incoming connections while an app is backgrounded. - * - * Example usage: - * - * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port - * { - * [asyncSocket performBlock:^{ - * [asyncSocket enableBackgroundingOnSocket]; - * }]; - * } -**/ -- (BOOL)enableBackgroundingOnSocket; - -#endif - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket. -**/ -- (SSLContextRef)sslContext; - -#pragma mark Utilities - -/** - * The address lookup utility used by the class. - * This method is synchronous, so it's recommended you use it on a background thread/queue. - * - * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6. - * - * @returns - * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo. - * The addresses are specifically for TCP connections. - * You can filter the addresses, if needed, using the other utility methods provided by the class. -**/ -+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr; - -/** - * Extracting host and port information from raw address data. -**/ - -+ (NSString *)hostFromAddress:(NSData *)address; -+ (uint16_t)portFromAddress:(NSData *)address; - -+ (BOOL)isIPv4Address:(NSData *)address; -+ (BOOL)isIPv6Address:(NSData *)address; - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address; - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address; - -/** - * A few common line separators, for use with the readDataToData:... methods. -**/ -+ (NSData *)CRLFData; // 0x0D0A -+ (NSData *)CRData; // 0x0D -+ (NSData *)LFData; // 0x0A -+ (NSData *)ZeroData; // 0x00 - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol GCDAsyncSocketDelegate -@optional - -/** - * This method is called immediately prior to socket:didAcceptNewSocket:. - * It optionally allows a listening socket to specify the socketQueue for a new accepted socket. - * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue. - * - * Since you cannot autorelease a dispatch_queue, - * this method uses the "new" prefix in its name to specify that the returned queue has been retained. - * - * Thus you could do something like this in the implementation: - * return dispatch_queue_create("MyQueue", NULL); - * - * If you are placing multiple sockets on the same queue, - * then care should be taken to increment the retain count each time this method is invoked. - * - * For example, your implementation might look something like this: - * dispatch_retain(myExistingQueue); - * return myExistingQueue; -**/ -- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; - -/** - * Called when a socket accepts a connection. - * Another socket is automatically spawned to handle it. - * - * You must retain the newSocket if you wish to handle the connection. - * Otherwise the newSocket instance will be released and the spawned connection will be closed. - * - * By default the new socket will have the same delegate and delegateQueue. - * You may, of course, change this at any time. -**/ -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket; - -/** - * Called when a socket connects and is ready for reading and writing. - * The host parameter will be an IP address, not a DNS name. -**/ -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; - -/** - * Called when a socket has completed reading the requested data into memory. - * Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; - -/** - * Called when a socket has read in data, but has not yet completed the read. - * This would occur if using readToData: or readToLength: methods. - * It may be used to for things such as updating progress bars. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called when a socket has completed writing the requested data. Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; - -/** - * Called when a socket has written some data, but has not yet completed the entire write. - * It may be used to for things such as updating progress bars. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called if a read operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been read so far for the read operation. - * - * Note that this method may be called multiple times for a single read if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Called if a write operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been written so far for the write operation. - * - * Note that this method may be called multiple times for a single write if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Conditionally called if the read stream closes, but the write stream may still be writeable. - * - * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO. - * See the discussion on the autoDisconnectOnClosedReadStream method for more information. -**/ -- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock; - -/** - * Called when a socket disconnects with or without error. - * - * If you call the disconnect method, and the socket wasn't already disconnected, - * then an invocation of this delegate method will be enqueued on the delegateQueue - * before the disconnect method returns. - * - * Note: If the GCDAsyncSocket instance is deallocated while it is still connected, - * and the delegate is not also deallocated, then this method will be invoked, - * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.) - * This is a generally rare, but is possible if one writes code like this: - * - * asyncSocket = nil; // I'm implicitly disconnecting the socket - * - * In this case it may preferrable to nil the delegate beforehand, like this: - * - * asyncSocket.delegate = nil; // Don't invoke my delegate method - * asyncSocket = nil; // I'm implicitly disconnecting the socket - * - * Of course, this depends on how your state machine is configured. -**/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err; - -/** - * Called after the socket has successfully completed SSL/TLS negotiation. - * This method is not called unless you use the provided startTLS method. - * - * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, - * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code. -**/ -- (void)socketDidSecure:(GCDAsyncSocket *)sock; - -/** - * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to. - * - * This is only called if startTLS is invoked with options that include: - * - GCDAsyncSocketManuallyEvaluateTrust == YES - * - * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer. - * - * Note from Apple's documentation: - * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain, - * [it] might block while attempting network access. You should never call it from your main thread; - * call it only from within a function running on a dispatch queue or on a separate thread. - * - * Thus this method uses a completionHandler block rather than a normal return value. - * The completionHandler block is thread-safe, and may be invoked from a background queue/thread. - * It is safe to invoke the completionHandler block even if the socket has been closed. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust - completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler; - -@end diff --git a/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m b/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m deleted file mode 100644 index 531a29d86c..0000000000 --- a/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +++ /dev/null @@ -1,7719 +0,0 @@ -// -// GCDAsyncSocket.m -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q4 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import "GCDAsyncSocket.h" - -#if TARGET_OS_IPHONE -#import -#endif - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC -#endif - - -#ifndef GCDAsyncSocketLoggingEnabled -#define GCDAsyncSocketLoggingEnabled 0 -#endif - -#if GCDAsyncSocketLoggingEnabled - -// Logging Enabled - See log level below - -// Logging uses the CocoaLumberjack framework (which is also GCD based). -// https://github.com/robbiehanson/CocoaLumberjack -// -// It allows us to do a lot of logging without significantly slowing down the code. -#import "DDLog.h" - -#define LogAsync YES -#define LogContext GCDAsyncSocketLoggingContext - -#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) -#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) - -#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) - -#ifndef GCDAsyncSocketLogLevel -#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE -#endif - -// Log levels : off, error, warn, info, verbose -static const int logLevel = GCDAsyncSocketLogLevel; - -#else - -// Logging Disabled - -#define LogError(frmt, ...) {} -#define LogWarn(frmt, ...) {} -#define LogInfo(frmt, ...) {} -#define LogVerbose(frmt, ...) {} - -#define LogCError(frmt, ...) {} -#define LogCWarn(frmt, ...) {} -#define LogCInfo(frmt, ...) {} -#define LogCVerbose(frmt, ...) {} - -#define LogTrace() {} -#define LogCTrace(frmt, ...) {} - -#endif - -/** - * Seeing a return statements within an inner block - * can sometimes be mistaken for a return point of the enclosing method. - * This makes inline blocks a bit easier to read. -**/ -#define return_from_block return - -/** - * A socket file descriptor is really just an integer. - * It represents the index of the socket within the kernel. - * This makes invalid file descriptor comparisons easier to read. -**/ -#define SOCKET_NULL -1 - - -NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException"; -NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain"; - -NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket"; -NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream"; - -NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust"; -#if TARGET_OS_IPHONE -NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS"; -#endif -NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID"; -NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; -NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; -NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart"; -NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord"; -NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; -#if !TARGET_OS_IPHONE -NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; -#endif - -enum GCDAsyncSocketFlags -{ - kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) - kConnected = 1 << 1, // If set, the socket is connected - kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed - kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout - kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout - kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued - kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued - kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. - kReadSourceSuspended = 1 << 8, // If set, the read source is suspended - kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended - kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS - kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete - kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete - kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS - kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket - kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained - kDealloc = 1 << 16, // If set, the socket is being deallocated -#if TARGET_OS_IPHONE - kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread - kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport - kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available -#endif -}; - -enum GCDAsyncSocketConfig -{ - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 - kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes -}; - -#if TARGET_OS_IPHONE - static NSThread *cfstreamThread; // Used for CFStreams - - static uint64_t cfstreamThreadRetainCount; // setup & teardown - static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * A PreBuffer is used when there is more data available on the socket - * than is being requested by current read request. - * In this case we slurp up all data from the socket (to minimize sys calls), - * and store additional yet unread data in a "prebuffer". - * - * The prebuffer is entirely drained before we read from the socket again. - * In other words, a large chunk of data is written is written to the prebuffer. - * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)). - * - * A ring buffer was once used for this purpose. - * But a ring buffer takes up twice as much memory as needed (double the size for mirroring). - * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size. - * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed. - * - * The current design is very simple and straight-forward, while also keeping memory requirements lower. -**/ - -@interface GCDAsyncSocketPreBuffer : NSObject -{ - uint8_t *preBuffer; - size_t preBufferSize; - - uint8_t *readPointer; - uint8_t *writePointer; -} - -- (id)initWithCapacity:(size_t)numBytes; - -- (void)ensureCapacityForWrite:(size_t)numBytes; - -- (size_t)availableBytes; -- (uint8_t *)readBuffer; - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr; - -- (size_t)availableSpace; -- (uint8_t *)writeBuffer; - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr; - -- (void)didRead:(size_t)bytesRead; -- (void)didWrite:(size_t)bytesWritten; - -- (void)reset; - -@end - -@implementation GCDAsyncSocketPreBuffer - -- (id)initWithCapacity:(size_t)numBytes -{ - if ((self = [super init])) - { - preBufferSize = numBytes; - preBuffer = malloc(preBufferSize); - - readPointer = preBuffer; - writePointer = preBuffer; - } - return self; -} - -- (void)dealloc -{ - if (preBuffer) - free(preBuffer); -} - -- (void)ensureCapacityForWrite:(size_t)numBytes -{ - size_t availableSpace = [self availableSpace]; - - if (numBytes > availableSpace) - { - size_t additionalBytes = numBytes - availableSpace; - - size_t newPreBufferSize = preBufferSize + additionalBytes; - uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize); - - size_t readPointerOffset = readPointer - preBuffer; - size_t writePointerOffset = writePointer - preBuffer; - - preBuffer = newPreBuffer; - preBufferSize = newPreBufferSize; - - readPointer = preBuffer + readPointerOffset; - writePointer = preBuffer + writePointerOffset; - } -} - -- (size_t)availableBytes -{ - return writePointer - readPointer; -} - -- (uint8_t *)readBuffer -{ - return readPointer; -} - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr -{ - if (bufferPtr) *bufferPtr = readPointer; - if (availableBytesPtr) *availableBytesPtr = [self availableBytes]; -} - -- (void)didRead:(size_t)bytesRead -{ - readPointer += bytesRead; - - if (readPointer == writePointer) - { - // The prebuffer has been drained. Reset pointers. - readPointer = preBuffer; - writePointer = preBuffer; - } -} - -- (size_t)availableSpace -{ - return preBufferSize - (writePointer - preBuffer); -} - -- (uint8_t *)writeBuffer -{ - return writePointer; -} - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr -{ - if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; -} - -- (void)didWrite:(size_t)bytesWritten -{ - writePointer += bytesWritten; -} - -- (void)reset -{ - readPointer = preBuffer; - writePointer = preBuffer; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncReadPacket encompasses the instructions for any given read. - * The content of a read packet allows the code to determine if we're: - * - reading to a certain length - * - reading to a certain separator - * - or simply reading the first chunk of available data -**/ -@interface GCDAsyncReadPacket : NSObject -{ - @public - NSMutableData *buffer; - NSUInteger startOffset; - NSUInteger bytesDone; - NSUInteger maxLength; - NSTimeInterval timeout; - NSUInteger readLength; - NSData *term; - BOOL bufferOwner; - NSUInteger originalBufferLength; - long tag; -} -- (id)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i; - -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead; - -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr; - -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable; -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr; -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr; - -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes; - -@end - -@implementation GCDAsyncReadPacket - -- (id)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i -{ - if((self = [super init])) - { - bytesDone = 0; - maxLength = m; - timeout = t; - readLength = l; - term = [e copy]; - tag = i; - - if (d) - { - buffer = d; - startOffset = s; - bufferOwner = NO; - originalBufferLength = [d length]; - } - else - { - if (readLength > 0) - buffer = [[NSMutableData alloc] initWithLength:readLength]; - else - buffer = [[NSMutableData alloc] initWithLength:0]; - - startOffset = 0; - bufferOwner = YES; - originalBufferLength = 0; - } - } - return self; -} - -/** - * Increases the length of the buffer (if needed) to ensure a read of the given size will fit. -**/ -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead -{ - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (bytesToRead > buffSpace) - { - NSUInteger buffInc = bytesToRead - buffSpace; - - [buffer increaseLengthBy:buffInc]; - } -} - -/** - * This method is used when we do NOT know how much data is available to be read from the socket. - * This method returns the default value unless it exceeds the specified readLength or maxLength. - * - * Furthermore, the shouldPreBuffer decision is based upon the packet type, - * and whether the returned value would fit in the current buffer without requiring a resize of the buffer. -**/ -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSUInteger result; - - if (readLength > 0) - { - // Read a specific length of data - - result = MIN(defaultValue, (readLength - bytesDone)); - - // There is no need to prebuffer since we know exactly how much data we need to read. - // Even if the buffer isn't currently big enough to fit this amount of data, - // it would have to be resized eventually anyway. - - if (shouldPreBufferPtr) - *shouldPreBufferPtr = NO; - } - else - { - // Either reading until we find a specified terminator, - // or we're simply reading all available data. - // - // In other words, one of: - // - // - readDataToData packet - // - readDataWithTimeout packet - - if (maxLength > 0) - result = MIN(defaultValue, (maxLength - bytesDone)); - else - result = defaultValue; - - // Since we don't know the size of the read in advance, - // the shouldPreBuffer decision is based upon whether the returned value would fit - // in the current buffer without requiring a resize of the buffer. - // - // This is because, in all likelyhood, the amount read from the socket will be less than the default value. - // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (buffSpace >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - } - - return result; -} - -/** - * For read packets without a set terminator, returns the amount of data - * that can be read without exceeding the readLength or maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * The given hint MUST be greater than zero. -**/ -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable -{ - NSAssert(term == nil, @"This method does not apply to term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - if (readLength > 0) - { - // Read a specific length of data - - return MIN(bytesAvailable, (readLength - bytesDone)); - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read a certain length of data that exceeds the size of the buffer, - // then it is clear that our code will resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - } - else - { - // Read all available data - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read all available data without giving us a maxLength, - // then it is clear that our code might resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - - return result; - } -} - -/** - * For read packets with a set terminator, returns the amount of data - * that can be read without exceeding the maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * To optimize memory allocations, mem copies, and mem moves - * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first, - * or if the data can be read directly into the read packet's buffer. -**/ -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // Should the data be read into the read packet's buffer, or into a pre-buffer first? - // - // One would imagine the preferred option is the faster one. - // So which one is faster? - // - // Reading directly into the packet's buffer requires: - // 1. Possibly resizing packet buffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) - // - // Reading into prebuffer first: - // 1. Possibly resizing prebuffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) - // 5. Removing underflow from prebuffer (memmove) - // - // Comparing the performance of the two we can see that reading - // data into the prebuffer first is slower due to the extra memove. - // - // However: - // The implementation of NSMutableData is open source via core foundation's CFMutableData. - // Decreasing the length of a mutable data object doesn't cause a realloc. - // In other words, the capacity of a mutable data object can grow, but doesn't shrink. - // - // This means the prebuffer will rarely need a realloc. - // The packet buffer, on the other hand, may often need a realloc. - // This is especially true if we are the buffer owner. - // Furthermore, if we are constantly realloc'ing the packet buffer, - // and then moving the overflow into the prebuffer, - // then we're consistently over-allocating memory for each term read. - // And now we get into a bit of a tradeoff between speed and memory utilization. - // - // The end result is that the two perform very similarly. - // And we can answer the original question very simply by another means. - // - // If we can read all the data directly into the packet's buffer without resizing it first, - // then we do so. Otherwise we use the prebuffer. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - if ((buffSize - buffUsed) >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - - return result; -} - -/** - * For read packets with a set terminator, - * returns the amount of data that can be read from the given preBuffer, - * without going over a terminator or the maxLength. - * - * It is assumed the terminator has not already been read. -**/ -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); - - // We know that the terminator, as a whole, doesn't exist in our own buffer. - // But it is possible that a _portion_ of it exists in our buffer. - // So we're going to look for the terminator starting with a portion of our own buffer. - // - // Example: - // - // term length = 3 bytes - // bytesDone = 5 bytes - // preBuffer length = 5 bytes - // - // If we append the preBuffer to our buffer, - // it would look like this: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // --------------------- - // - // So we start our search here: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // -------^-^-^--------- - // - // And move forwards... - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------^-^-^------- - // - // Until we find the terminator or reach the end. - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------------^-^-^- - - BOOL found = NO; - - NSUInteger termLength = [term length]; - NSUInteger preBufferLength = [preBuffer availableBytes]; - - if ((bytesDone + preBufferLength) < termLength) - { - // Not enough data for a full term sequence yet - return preBufferLength; - } - - NSUInteger maxPreBufferLength; - if (maxLength > 0) { - maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); - - // Note: maxLength >= termLength - } - else { - maxPreBufferLength = preBufferLength; - } - - uint8_t seq[termLength]; - const void *termBuf = [term bytes]; - - NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); - uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; - - NSUInteger preLen = termLength - bufLen; - const uint8_t *pre = [preBuffer readBuffer]; - - NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. - - NSUInteger result = maxPreBufferLength; - - NSUInteger i; - for (i = 0; i < loopCount; i++) - { - if (bufLen > 0) - { - // Combining bytes from buffer and preBuffer - - memcpy(seq, buf, bufLen); - memcpy(seq + bufLen, pre, preLen); - - if (memcmp(seq, termBuf, termLength) == 0) - { - result = preLen; - found = YES; - break; - } - - buf++; - bufLen--; - preLen++; - } - else - { - // Comparing directly from preBuffer - - if (memcmp(pre, termBuf, termLength) == 0) - { - NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic - - result = preOffset + termLength; - found = YES; - break; - } - - pre++; - } - } - - // There is no need to avoid resizing the buffer in this particular situation. - - if (foundPtr) *foundPtr = found; - return result; -} - -/** - * For read packets with a set terminator, scans the packet buffer for the term. - * It is assumed the terminator had not been fully read prior to the new bytes. - * - * If the term is found, the number of excess bytes after the term are returned. - * If the term is not found, this method will return -1. - * - * Note: A return value of zero means the term was found at the very end. - * - * Prerequisites: - * The given number of bytes have been added to the end of our buffer. - * Our bytesDone variable has NOT been changed due to the prebuffered bytes. -**/ -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - - // The implementation of this method is very similar to the above method. - // See the above method for a discussion of the algorithm used here. - - uint8_t *buff = [buffer mutableBytes]; - NSUInteger buffLength = bytesDone + numBytes; - - const void *termBuff = [term bytes]; - NSUInteger termLength = [term length]; - - // Note: We are dealing with unsigned integers, - // so make sure the math doesn't go below zero. - - NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; - - while (i + termLength <= buffLength) - { - uint8_t *subBuffer = buff + startOffset + i; - - if (memcmp(subBuffer, termBuff, termLength) == 0) - { - return buffLength - (i + termLength); - } - - i++; - } - - return -1; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncWritePacket encompasses the instructions for any given write. -**/ -@interface GCDAsyncWritePacket : NSObject -{ - @public - NSData *buffer; - NSUInteger bytesDone; - long tag; - NSTimeInterval timeout; -} -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; -@end - -@implementation GCDAsyncWritePacket - -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - buffer = d; // Retain not copy. For performance as documented in header file. - bytesDone = 0; - timeout = t; - tag = i; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. - * This class my be altered to support more than just TLS in the future. -**/ -@interface GCDAsyncSpecialPacket : NSObject -{ - @public - NSDictionary *tlsSettings; -} -- (id)initWithTLSSettings:(NSDictionary *)settings; -@end - -@implementation GCDAsyncSpecialPacket - -- (id)initWithTLSSettings:(NSDictionary *)settings -{ - if((self = [super init])) - { - tlsSettings = [settings copy]; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation GCDAsyncSocket -{ - uint32_t flags; - uint16_t config; - - __weak id delegate; - dispatch_queue_t delegateQueue; - - int socket4FD; - int socket6FD; - int stateIndex; - NSData * connectInterface4; - NSData * connectInterface6; - - dispatch_queue_t socketQueue; - - dispatch_source_t accept4Source; - dispatch_source_t accept6Source; - dispatch_source_t connectTimer; - dispatch_source_t readSource; - dispatch_source_t writeSource; - dispatch_source_t readTimer; - dispatch_source_t writeTimer; - - NSMutableArray *readQueue; - NSMutableArray *writeQueue; - - GCDAsyncReadPacket *currentRead; - GCDAsyncWritePacket *currentWrite; - - unsigned long socketFDBytesAvailable; - - GCDAsyncSocketPreBuffer *preBuffer; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; -#endif - SSLContextRef sslContext; - GCDAsyncSocketPreBuffer *sslPreBuffer; - size_t sslWriteCachedLength; - OSStatus sslErrCode; - - void *IsOnSocketQueueOrTargetQueueKey; - - id userData; -} - -- (id)init -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; -} - -- (id)initWithSocketQueue:(dispatch_queue_t)sq -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; -} - -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq -{ - return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; -} - -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq -{ - if((self = [super init])) - { - delegate = aDelegate; - delegateQueue = dq; - - #if !OS_OBJECT_USE_OBJC - if (dq) dispatch_retain(dq); - #endif - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - stateIndex = 0; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(sq); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); - - readQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentRead = nil; - - writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentWrite = nil; - - preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - } - return self; -} - -- (void)dealloc -{ - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - - // Set dealloc flag. - // This is used by closeWithError to ensure we don't accidentally retain ourself. - flags |= kDealloc; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; - - #if !OS_OBJECT_USE_OBJC - if (socketQueue) dispatch_release(socketQueue); - #endif - socketQueue = NULL; - - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result; - - dispatch_sync(socketQueue, ^{ - result = delegate; - }); - - return result; - } -} - -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - delegate = newDelegate; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:YES]; -} - -- (dispatch_queue_t)delegateQueue -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result; - - dispatch_sync(socketQueue, ^{ - result = delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:YES]; -} - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = delegate; - dqPtr = delegateQueue; - }); - - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - delegate = newDelegate; - - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; -} - -- (BOOL)isIPv4Enabled -{ - // Note: YES means kIPv4Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv4Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kIPv4Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv4Enabled:(BOOL)flag -{ - // Note: YES means kIPv4Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kIPv4Disabled; - else - config |= kIPv4Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv6Enabled -{ - // Note: YES means kIPv6Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv6Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kIPv6Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv6Enabled:(BOOL)flag -{ - // Note: YES means kIPv6Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kIPv6Disabled; - else - config |= kIPv6Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4PreferredOverIPv6 -{ - // Note: YES means kPreferIPv6 is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kPreferIPv6) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kPreferIPv6) == 0); - }); - - return result; - } -} - -- (void)setIPv4PreferredOverIPv6:(BOOL)flag -{ - // Note: YES means kPreferIPv6 is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kPreferIPv6; - else - config |= kPreferIPv6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (id)userData -{ - __block id result = nil; - - dispatch_block_t block = ^{ - - result = userData; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setUserData:(id)arbitraryUserData -{ - dispatch_block_t block = ^{ - - if (userData != arbitraryUserData) - { - userData = arbitraryUserData; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accepting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self acceptOnInterface:nil port:port error:errPtr]; -} - -- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - // Just in-case interface parameter is immutable. - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - // CreateSocket Block - // This block will be invoked within the dispatch block below. - - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - - int socketFD = socket(domain, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errnoErrorWithReason:reason]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - int reuseOn = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Bind socket - - status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - // Listen - - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create dispatch block and run on socketQueue - - dispatch_block_t block = ^{ @autoreleasepool { - - if (delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; - - return_from_block; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - // Resolve interface from description - - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; - - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); - - // Create sockets, configure, bind, and listen - - if (enableIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - socket4FD = createSocket(AF_INET, interface4); - - if (socket4FD == SOCKET_NULL) - { - return_from_block; - } - } - - if (enableIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - if (enableIPv4 && (port == 0)) - { - // No specific port was specified, so we allowed the OS to pick an available port for us. - // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. - - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; - addr6->sin6_port = htons([self localPort4]); - } - - socket6FD = createSocket(AF_INET6, interface6); - - if (socket6FD == SOCKET_NULL) - { - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - } - - return_from_block; - } - } - - // Create accept sources - - if (enableIPv4) - { - accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); - - int socketFD = socket4FD; - dispatch_source_t acceptSource = accept4Source; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"event4Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - - #pragma clang diagnostic pop - }}); - - - dispatch_source_set_cancel_handler(accept4Source, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - - #pragma clang diagnostic pop - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(accept4Source); - } - - if (enableIPv6) - { - accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); - - int socketFD = socket6FD; - dispatch_source_t acceptSource = accept6Source; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"event6Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - - #pragma clang diagnostic pop - }}); - - dispatch_source_set_cancel_handler(accept6Source, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept6Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket6FD)"); - close(socketFD); - - #pragma clang diagnostic pop - }); - - LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(accept6Source); - } - - flags |= kSocketStarted; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); - - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)doAccept:(int)parentSocketFD -{ - LogTrace(); - - BOOL isIPv4; - int childSocketFD; - NSData *childSocketAddress; - - if (parentSocketFD == socket4FD) - { - isIPv4 = YES; - - struct sockaddr_in addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else // if (parentSocketFD == socket6FD) - { - isIPv4 = NO; - - struct sockaddr_in6 addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - - // Enable non-blocking IO on the socket - - int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); - return NO; - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Notify delegate - - if (delegateQueue) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - // Query delegate for custom socket queue - - dispatch_queue_t childSocketQueue = NULL; - - if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) - { - childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress - onSocket:self]; - } - - // Create GCDAsyncSocket instance for accepted socket - - GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:theDelegate - delegateQueue:delegateQueue - socketQueue:childSocketQueue]; - - if (isIPv4) - acceptedSocket->socket4FD = childSocketFD; - else - acceptedSocket->socket6FD = childSocketFD; - - acceptedSocket->flags = (kSocketStarted | kConnected); - - // Setup read and write sources for accepted socket - - dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool { - - [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; - }}); - - // Notify delegate - - if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) - { - [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; - } - - // Release the socket queue returned from the delegate (it was retained by acceptedSocket) - #if !OS_OBJECT_USE_OBJC - if (childSocketQueue) dispatch_release(childSocketQueue); - #endif - - // The accepted socket should have been retained by the delegate. - // Otherwise it gets properly released when exiting the block. - }}); - } - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a connection attempt. - * It is shared between the connectToHost and connectToAddress methods. - * -**/ -- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (interface) - { - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; - - if ((interface4 == nil) && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterface4 = interface4; - connectInterface6 = interface6; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)inHost - onPort:(uint16_t)port - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSString *host = [inHost copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *preConnectErr = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([host length] == 0) - { - NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; - preConnectErr = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&preConnectErr]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - flags |= kSocketStarted; - - LogVerbose(@"Dispatching DNS lookup..."); - - // It's possible that the given host parameter is actually a NSMutableString. - // So we want to copy it now, within this block that will be executed synchronously. - // This way the asynchronous lookup block below doesn't have to worry about it changing. - - NSString *hostCpy = [host copy]; - - int aStateIndex = stateIndex; - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - NSError *lookupErr = nil; - NSMutableArray *addresses = [GCDAsyncSocket lookupHost:hostCpy port:port error:&lookupErr]; - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - if (lookupErr) - { - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf lookup:aStateIndex didFail:lookupErr]; - }}); - } - else - { - NSData *address4 = nil; - NSData *address6 = nil; - - for (NSData *address in addresses) - { - if (!address4 && [GCDAsyncSocket isIPv4Address:address]) - { - address4 = address; - } - else if (!address6 && [GCDAsyncSocket isIPv6Address:address]) - { - address6 = address; - } - } - - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } - - #pragma clang diagnostic pop - }}); - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - - if (errPtr) *errPtr = preConnectErr; - return result; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)inRemoteAddr - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSData *remoteAddr = [inRemoteAddr copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with remoteAddr parameter - - NSData *address4 = nil; - NSData *address6 = nil; - - if ([remoteAddr length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; - - if (sockaddr->sa_family == AF_INET) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in)) - { - address4 = remoteAddr; - } - } - else if (sockaddr->sa_family == AF_INET6) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in6)) - { - address6 = remoteAddr; - } - } - } - - if ((address4 == nil) && (address6 == nil)) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address4 != nil)) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (address6 != nil)) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - return_from_block; - } - - flags |= kSocketStarted; - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(address4 || address6, @"Expected at least one valid address"); - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - // Check for problems - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - if (isIPv6Disabled && (address4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - // Start the normal connection process - - NSError *err = nil; - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - [self closeWithError:err]; - } -} - -/** - * This method is called if the DNS lookup fails. - * This method is executed on the socketQueue. - * - * Since the DNS lookup executed synchronously on a global concurrent queue, - * the original connection request may have already been cancelled or timed-out by the time this method is invoked. - * The lookupIndex tells us whether the lookup is still valid or not. -**/ -- (void)lookup:(int)aStateIndex didFail:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring lookup:didFail: - already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self endConnectTimeout]; - [self closeWithError:error]; -} - -- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]); - LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]); - - // Determine socket type - - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); - - // Create the socket - - int socketFD; - NSData *address; - NSData *connectInterface; - - if (useIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = socket(AF_INET6, SOCK_STREAM, 0); - - socketFD = socket6FD; - address = address6; - connectInterface = connectInterface6; - } - else - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = socket(AF_INET, SOCK_STREAM, 0); - - socketFD = socket4FD; - address = address4; - connectInterface = connectInterface4; - } - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return NO; - } - - // Bind the socket to the desired interface (if needed) - - if (connectInterface) - { - LogVerbose(@"Binding socket..."); - - if ([[self class] portFromAddress:connectInterface] > 0) - { - // Since we're going to be binding to a specific port, - // we should turn on reuseaddr to allow us to override sockets in time_wait. - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - } - - const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; - - int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]); - if (result != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; - - return NO; - } - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Start the connection process in a background queue - - int aStateIndex = stateIndex; - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - if (result == 0) - { - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf didConnect:aStateIndex]; - }}); - } - else - { - NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"]; - - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf didNotConnect:aStateIndex error:error]; - }}); - } - - #pragma clang diagnostic pop - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (void)didConnect:(int)aStateIndex -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring didConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - flags |= kConnected; - - [self endConnectTimeout]; - - #if TARGET_OS_IPHONE - // The endConnectTimeout method executed above incremented the stateIndex. - aStateIndex = stateIndex; - #endif - - // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) - // - // Note: - // There may be configuration options that must be set by the delegate before opening the streams. - // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. - // - // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. - // This gives the delegate time to properly configure the streams if needed. - - dispatch_block_t SetupStreamsPart1 = ^{ - #if TARGET_OS_IPHONE - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:NO]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - #endif - }; - dispatch_block_t SetupStreamsPart2 = ^{ - #if TARGET_OS_IPHONE - - if (aStateIndex != stateIndex) - { - // The socket has been disconnected. - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - #endif - }; - - // Notify delegate - - NSString *host = [self connectedHost]; - uint16_t port = [self connectedPort]; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) - { - SetupStreamsPart1(); - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToHost:host port:port]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else - { - SetupStreamsPart1(); - SetupStreamsPart2(); - } - - // Get the connected socket - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD; - - // Enable non-blocking IO on the socket - - int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; - [self closeWithError:[self otherError:errMsg]]; - - return; - } - - // Setup our read/write sources - - [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; - - // Dequeue any pending read/write requests - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; -} - -- (void)didNotConnect:(int)aStateIndex error:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring didNotConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self closeWithError:error]; -} - -- (void)startConnectTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doConnectTimeout]; - - #pragma clang diagnostic pop - }}); - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theConnectTimer = connectTimer; - dispatch_source_set_cancel_handler(connectTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(connectTimer)"); - dispatch_release(theConnectTimer); - - #pragma clang diagnostic pop - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); - - dispatch_resume(connectTimer); - } -} - -- (void)endConnectTimeout -{ - LogTrace(); - - if (connectTimer) - { - dispatch_source_cancel(connectTimer); - connectTimer = NULL; - } - - // Increment stateIndex. - // This will prevent us from processing results from any related background asynchronous operations. - // - // Note: This should be called from close method even if connectTimer is NULL. - // This is because one might disconnect a socket prior to a successful connection which had no timeout. - - stateIndex++; - - if (connectInterface4) - { - connectInterface4 = nil; - } - if (connectInterface6) - { - connectInterface6 = nil; - } -} - -- (void)doConnectTimeout -{ - LogTrace(); - - [self endConnectTimeout]; - [self closeWithError:[self connectTimeoutError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)closeWithError:(NSError *)error -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - [self endConnectTimeout]; - - if (currentRead != nil) [self endCurrentRead]; - if (currentWrite != nil) [self endCurrentWrite]; - - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - [preBuffer reset]; - - #if TARGET_OS_IPHONE - { - if (readStream || writeStream) - { - [self removeStreamsFromRunLoop]; - - if (readStream) - { - CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - } - } - #endif - - [sslPreBuffer reset]; - sslErrCode = noErr; - - if (sslContext) - { - // Getting a linker error here about the SSLx() functions? - // You need to add the Security Framework to your application. - - SSLClose(sslContext); - - #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - CFRelease(sslContext); - #else - SSLDisposeContext(sslContext); - #endif - - sslContext = NULL; - } - - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - - if (!accept4Source && !accept6Source && !readSource && !writeSource) - { - LogVerbose(@"manually closing close"); - - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"close(socket6FD)"); - close(socket6FD); - socket6FD = SOCKET_NULL; - } - } - else - { - if (accept4Source) - { - LogVerbose(@"dispatch_source_cancel(accept4Source)"); - dispatch_source_cancel(accept4Source); - - // We never suspend accept4Source - - accept4Source = NULL; - } - - if (accept6Source) - { - LogVerbose(@"dispatch_source_cancel(accept6Source)"); - dispatch_source_cancel(accept6Source); - - // We never suspend accept6Source - - accept6Source = NULL; - } - - if (readSource) - { - LogVerbose(@"dispatch_source_cancel(readSource)"); - dispatch_source_cancel(readSource); - - [self resumeReadSource]; - - readSource = NULL; - } - - if (writeSource) - { - LogVerbose(@"dispatch_source_cancel(writeSource)"); - dispatch_source_cancel(writeSource); - - [self resumeWriteSource]; - - writeSource = NULL; - } - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - } - - // If the client has passed the connect/accept method, then the connection has at least begun. - // Notify delegate that it is now ending. - BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; - BOOL isDeallocating = (flags & kDealloc) ? YES : NO; - - // Clear stored socket info and all flags (config remains as is) - socketFDBytesAvailable = 0; - flags = 0; - - if (shouldCallDelegate) - { - __strong id theDelegate = delegate; - __strong id theSelf = isDeallocating ? nil : self; - - if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidDisconnect:theSelf withError:error]; - }}); - } - } -} - -- (void)disconnect -{ - dispatch_block_t block = ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - [self closeWithError:nil]; - } - }}; - - // Synchronous disconnection, as documented in the header file - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (void)disconnectAfterReading -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterReads); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterReadingAndWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -/** - * Closes the socket if possible. - * That is, if all writes have completed, and we're set to disconnect after writing, - * or if all reads have completed, and we're set to disconnect after reading. -**/ -- (void)maybeClose -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - BOOL shouldClose = NO; - - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - else - { - shouldClose = YES; - } - } - } - else if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - - if (shouldClose) - { - [self closeWithError:nil]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; -} - -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; -} - -+ (NSError *)gaiError:(int)gai_error -{ - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; -} - -- (NSError *)errnoErrorWithReason:(NSString *)reason -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, - reason, NSLocalizedFailureReasonErrorKey, nil]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)errnoError -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)sslError:(OSStatus)ssl_error -{ - NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; -} - -- (NSError *)connectTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Attempt to connect to host timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket maxed out error. -**/ -- (NSError *)readMaxedOutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation reached set maximum length", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)readTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)writeTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Write operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; -} - -- (NSError *)connectionClosedError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Socket closed by remote peer", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; -} - -- (NSError *)otherError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)isDisconnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kSocketStarted) ? NO : YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isConnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kConnected) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSString *)connectedHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socket4FD != SOCKET_NULL) - result = [self connectedHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedHostFromSocket6:socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)connectedPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool - - if (socket4FD != SOCKET_NULL) - result = [self connectedPortFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedPortFromSocket6:socket6FD]; - }); - - return result; - } -} - -- (NSString *)localHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socket4FD != SOCKET_NULL) - result = [self localHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localHostFromSocket6:socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)localPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool - - if (socket4FD != SOCKET_NULL) - result = [self localPortFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localPortFromSocket6:socket6FD]; - }); - - return result; - } -} - -- (NSString *)connectedHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)connectedHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)connectedPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)connectedPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)localHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)localHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)localPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)localPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)connectedHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)connectedHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)connectedPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)connectedPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSString *)localHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)localHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)localPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)localPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSData *)connectedAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSData *)localAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv4 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket4FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (socket4FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isIPv6 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket6FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (socket6FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isSecure -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (flags & kSocketSecure) ? YES : NO; - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = (flags & kSocketSecure) ? YES : NO; - }); - - return result; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Finds the address of an interface description. - * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). - * - * The interface description may optionally contain a port number at the end, separated by a colon. - * If a non-zero port parameter is provided, any port number in the interface description is ignored. - * - * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object. -**/ -- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr - address6:(NSMutableData **)interfaceAddr6Ptr - fromDescription:(NSString *)interfaceDescription - port:(uint16_t)port -{ - NSMutableData *addr4 = nil; - NSMutableData *addr6 = nil; - - NSString *interface = nil; - - NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; - if ([components count] > 0) - { - NSString *temp = [components objectAtIndex:0]; - if ([temp length] > 0) - { - interface = temp; - } - } - if ([components count] > 1 && port == 0) - { - long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10); - - if (portL > 0 && portL <= UINT16_MAX) - { - port = (uint16_t)portL; - } - } - - if (interface == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interface UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in nativeAddr4; - memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 - - struct sockaddr_in6 nativeAddr6; - memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - } - - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; -} - -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD -{ - readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); - writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue); - - // Setup event handlers - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"readEventBlock"); - - strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable); - - if (strongSelf->socketFDBytesAvailable > 0) - [strongSelf doReadData]; - else - [strongSelf doReadEOF]; - - #pragma clang diagnostic pop - }}); - - dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"writeEventBlock"); - - strongSelf->flags |= kSocketCanAcceptBytes; - [strongSelf doWriteData]; - - #pragma clang diagnostic pop - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadSource = readSource; - dispatch_source_t theWriteSource = writeSource; - #endif - - dispatch_source_set_cancel_handler(readSource, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"readCancelBlock"); - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(readSource)"); - dispatch_release(theReadSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - - #pragma clang diagnostic pop - }); - - dispatch_source_set_cancel_handler(writeSource, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"writeCancelBlock"); - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(writeSource)"); - dispatch_release(theWriteSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - - #pragma clang diagnostic pop - }); - - // We will not be able to read until data arrives. - // But we should be able to write immediately. - - socketFDBytesAvailable = 0; - flags &= ~kReadSourceSuspended; - - LogVerbose(@"dispatch_resume(readSource)"); - dispatch_resume(readSource); - - flags |= kSocketCanAcceptBytes; - flags |= kWriteSourceSuspended; -} - -- (BOOL)usingCFStreamForTLS -{ - #if TARGET_OS_IPHONE - - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - - return YES; - } - - #endif - - return NO; -} - -- (BOOL)usingSecureTransportForTLS -{ - // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) - - #if TARGET_OS_IPHONE - - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - - return NO; - } - - #endif - - return YES; -} - -- (void)suspendReadSource -{ - if (!(flags & kReadSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(readSource)"); - - dispatch_suspend(readSource); - flags |= kReadSourceSuspended; - } -} - -- (void)resumeReadSource -{ - if (flags & kReadSourceSuspended) - { - LogVerbose(@"dispatch_resume(readSource)"); - - dispatch_resume(readSource); - flags &= ~kReadSourceSuspended; - } -} - -- (void)suspendWriteSource -{ - if (!(flags & kWriteSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(writeSource)"); - - dispatch_suspend(writeSource); - flags |= kWriteSourceSuspended; - } -} - -- (void)resumeWriteSource -{ - if (flags & kWriteSourceSuspended) - { - LogVerbose(@"dispatch_resume(writeSource)"); - - dispatch_resume(writeSource); - flags &= ~kWriteSourceSuspended; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reading -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag -{ - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; -} - -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - if (length == 0) { - LogWarn(@"Cannot read: length == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:0 - timeout:timeout - readLength:length - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)maxLength - tag:(long)tag -{ - if ([data length] == 0) { - LogWarn(@"Cannot read: [data length] == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - if (maxLength > 0 && maxLength < [data length]) { - LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:maxLength - timeout:timeout - readLength:0 - terminator:data - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!currentRead || ![currentRead isKindOfClass:[GCDAsyncReadPacket class]]) - { - // We're not reading anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - // It's only possible to know the progress of our read if we're reading to a certain length. - // If we're reading to data, we of course have no idea when the data will arrive. - // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. - - NSUInteger done = currentRead->bytesDone; - NSUInteger total = currentRead->readLength; - - if (tagPtr != NULL) *tagPtr = currentRead->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - if (total > 0) - result = (float)done / (float)total; - else - result = 1.0F; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * This method starts a new read, if needed. - * - * It is called when: - * - a user requests a read - * - after a read request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueRead -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - // If we're not currently processing a read AND we have an available read stream - if ((currentRead == nil) && (flags & kConnected)) - { - if ([readQueue count] > 0) - { - // Dequeue the next object in the write queue - currentRead = [readQueue objectAtIndex:0]; - [readQueue removeObjectAtIndex:0]; - - - if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingReadTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncReadPacket"); - - // Setup read timer (if needed) - [self setupReadTimerWithTimeout:currentRead->timeout]; - - // Immediately read, if possible - [self doReadData]; - } - } - else if (flags & kDisconnectAfterReads) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - else if (flags & kSocketSecure) - { - [self flushSSLBuffers]; - - // Edge case: - // - // We just drained all data from the ssl buffers, - // and all known data from the socket (socketFDBytesAvailable). - // - // If we didn't get any data from this process, - // then we may have reached the end of the TCP stream. - // - // Be sure callbacks are enabled so we're notified about a disconnection. - - if ([preBuffer availableBytes] == 0) - { - if ([self usingCFStreamForTLS]) { - // Callbacks never disabled - } - else { - [self resumeReadSource]; - } - } - } - } -} - -- (void)flushSSLBuffers -{ - LogTrace(); - - NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket"); - - if ([preBuffer availableBytes] > 0) - { - // Only flush the ssl buffers if the prebuffer is empty. - // This is to avoid growing the prebuffer inifinitely large. - - return; - } - - #if TARGET_OS_IPHONE - - if ([self usingCFStreamForTLS]) - { - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - CFIndex defaultBytesToRead = (1024 * 4); - - [preBuffer ensureCapacityForWrite:defaultBytesToRead]; - - uint8_t *buffer = [preBuffer writeBuffer]; - - CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); - LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result); - - if (result > 0) - { - [preBuffer didWrite:result]; - } - - flags &= ~kSecureSocketHasBytesAvailable; - } - - return; - } - - #endif - - __block NSUInteger estimatedBytesAvailable = 0; - - dispatch_block_t updateEstimatedBytesAvailable = ^{ - - // Figure out if there is any data available to be read - // - // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket - // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket - // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered - // - // We call the variable "estimated" because we don't know how many decrypted bytes we'll get - // from the encrypted bytes in the sslPreBuffer. - // However, we do know this is an upper bound on the estimation. - - estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes]; - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - }; - - updateEstimatedBytesAvailable(); - - if (estimatedBytesAvailable > 0) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - BOOL done = NO; - do - { - LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable); - - // Make sure there's enough room in the prebuffer - - [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; - - // Read data into prebuffer - - uint8_t *buffer = [preBuffer writeBuffer]; - size_t bytesRead = 0; - - OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead); - LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead); - - if (bytesRead > 0) - { - [preBuffer didWrite:bytesRead]; - } - - LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]); - - if (result != noErr) - { - done = YES; - } - else - { - updateEstimatedBytesAvailable(); - } - - } while (!done && estimatedBytesAvailable > 0); - } -} - -- (void)doReadData -{ - LogTrace(); - - // This method is called on the socketQueue. - // It might be called directly, or via the readSource when data is available to be read. - - if ((currentRead == nil) || (flags & kReadsPaused)) - { - LogVerbose(@"No currentRead or kReadsPaused"); - - // Unable to read at this time - - if (flags & kSocketSecure) - { - // Here's the situation: - // - // We have an established secure connection. - // There may not be a currentRead, but there might be encrypted data sitting around for us. - // When the user does get around to issuing a read, that encrypted data will need to be decrypted. - // - // So why make the user wait? - // We might as well get a head start on decrypting some data now. - // - // The other reason we do this has to do with detecting a socket disconnection. - // The SSL/TLS protocol has it's own disconnection handshake. - // So when a secure socket is closed, a "goodbye" packet comes across the wire. - // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. - - [self flushSSLBuffers]; - } - - if ([self usingCFStreamForTLS]) - { - // CFReadStream only fires once when there is available data. - // It won't fire again until we've invoked CFReadStreamRead. - } - else - { - // If the readSource is firing, we need to pause it - // or else it will continue to fire over and over again. - // - // If the readSource is not firing, - // we want it to continue monitoring the socket. - - if (socketFDBytesAvailable > 0) - { - [self suspendReadSource]; - } - } - return; - } - - BOOL hasBytesAvailable = NO; - unsigned long estimatedBytesAvailable = 0; - - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) - - estimatedBytesAvailable = 0; - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - hasBytesAvailable = YES; - else - hasBytesAvailable = NO; - - #endif - } - else - { - estimatedBytesAvailable = socketFDBytesAvailable; - - if (flags & kSocketSecure) - { - // There are 2 buffers to be aware of here. - // - // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. - // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. - // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. - // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. - // - // The first buffer is one we create. - // SecureTransport often requests small amounts of data. - // This has to do with the encypted packets that are coming across the TCP stream. - // But it's non-optimal to do a bunch of small reads from the BSD socket. - // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) - // and may store excess in the sslPreBuffer. - - estimatedBytesAvailable += [sslPreBuffer availableBytes]; - - // The second buffer is within SecureTransport. - // As mentioned earlier, there are encrypted packets coming across the TCP stream. - // SecureTransport needs the entire packet to decrypt it. - // But if the entire packet produces X bytes of decrypted data, - // and we only asked SecureTransport for X/2 bytes of data, - // it must store the extra X/2 bytes of decrypted data for the next read. - // - // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. - // From the documentation: - // - // "This function does not block or cause any low-level read operations to occur." - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - } - - hasBytesAvailable = (estimatedBytesAvailable > 0); - } - - if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) - { - LogVerbose(@"No data available to read..."); - - // No data available to read. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - } - return; - } - - if (flags & kStartingReadTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The readQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingWriteTLS) - { - if ([self usingSecureTransportForTLS]) - { - // We are in the process of a SSL Handshake. - // We were waiting for incoming data which has just arrived. - - [self ssl_continueSSLHandshake]; - } - } - else - { - // We are still waiting for the writeQueue to drain and start the SSL/TLS process. - // We now know data is available to read. - - if (![self usingCFStreamForTLS]) - { - // Suspend the read source or else it will continue to fire nonstop. - - [self suspendReadSource]; - } - } - - return; - } - - BOOL done = NO; // Completed read operation - NSError *error = nil; // Error occured - - NSUInteger totalBytesReadForCurrentRead = 0; - - // - // STEP 1 - READ FROM PREBUFFER - // - - if ([preBuffer availableBytes] > 0) - { - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - NSUInteger bytesToCopy; - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - } - else - { - // Read type #1 or #2 - - bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; - } - - // Make sure we have enough room in the buffer for our read. - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into packet buffer - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + - currentRead->bytesDone; - - memcpy(buffer, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the preBuffer - [preBuffer didRead:bytesToCopy]; - - LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]); - - // Update totals - - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - // - // We're done as soon as - // - we've read all available data (in prebuffer and socket) - // - we've read the maxLength of read packet. - - done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); - } - - } - - // - // STEP 2 - READ FROM SOCKET - // - - BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file) - BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - - if (!done && !error && !socketEOF && hasBytesAvailable) - { - NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); - - BOOL readIntoPreBuffer = NO; - uint8_t *buffer = NULL; - size_t bytesRead = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Using CFStream, rather than SecureTransport, for TLS - - NSUInteger defaultReadLength = (1024 * 32); - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); - LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); - } - else if (result == 0) - { - socketEOF = YES; - } - else - { - waiting = YES; - bytesRead = (size_t)result; - } - - // We only know how many decrypted bytes were read. - // The actual number of bytes read was likely more due to the overhead of the encryption. - // So we reset our flag, and rely on the next callback to alert us of more data. - flags &= ~kSecureSocketHasBytesAvailable; - - #endif - } - else - { - // Using SecureTransport for TLS - // - // We know: - // - how many bytes are available on the socket - // - how many encrypted bytes are sitting in the sslPreBuffer - // - how many decypted bytes are sitting in the sslContext - // - // But we do NOT know: - // - how many encypted bytes are sitting in the sslContext - // - // So we play the regular game of using an upper bound instead. - - NSUInteger defaultReadLength = (1024 * 32); - - if (defaultReadLength < estimatedBytesAvailable) { - defaultReadLength = estimatedBytesAvailable + (1024 * 16); - } - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // The documentation from Apple states: - // - // "a read operation might return errSSLWouldBlock, - // indicating that less data than requested was actually transferred" - // - // However, starting around 10.7, the function will sometimes return noErr, - // even if it didn't read as much data as requested. So we need to watch out for that. - - OSStatus result; - do - { - void *loop_buffer = buffer + bytesRead; - size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; - size_t loop_bytesRead = 0; - - result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead); - LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead); - - bytesRead += loop_bytesRead; - - } while ((result == noErr) && (bytesRead < bytesToRead)); - - - if (result != noErr) - { - if (result == errSSLWouldBlock) - waiting = YES; - else - { - if (result == errSSLClosedGraceful || result == errSSLClosedAbort) - { - // We've reached the end of the stream. - // Handle this the same way we would an EOF from the socket. - socketEOF = YES; - sslErrCode = result; - } - else - { - error = [self sslError:result]; - } - } - // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. - // This happens when the SSLRead function is able to read some data, - // but not the entire amount we requested. - - if (bytesRead <= 0) - { - bytesRead = 0; - } - } - - // Do not modify socketFDBytesAvailable. - // It will be updated via the SSLReadFunction(). - } - } - else - { - // Normal socket operation - - NSUInteger bytesToRead; - - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - // Read type #1 or #2 - - bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; - } - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); - LogVerbose(@"read from socket = %i", (int)result); - - if (result < 0) - { - if (errno == EWOULDBLOCK) - waiting = YES; - else - error = [self errnoErrorWithReason:@"Error in read() function"]; - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - socketEOF = YES; - socketFDBytesAvailable = 0; - } - else - { - bytesRead = result; - - if (bytesRead < bytesToRead) - { - // The read returned less data than requested. - // This means socketFDBytesAvailable was a bit off due to timing, - // because we read from the socket right when the readSource event was firing. - socketFDBytesAvailable = 0; - } - else - { - if (socketFDBytesAvailable <= bytesRead) - socketFDBytesAvailable = 0; - else - socketFDBytesAvailable -= bytesRead; - } - - if (socketFDBytesAvailable == 0) - { - waiting = YES; - } - } - } - - if (bytesRead > 0) - { - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - // - // Note: We should never be using a prebuffer when we're reading a specific length of data. - - NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - if (readIntoPreBuffer) - { - // We just read a big chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]); - - // Search for the terminating sequence - - NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy); - - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToCopy]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Update totals - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above - } - else - { - // We just read a big chunk of data directly into the packet's buffer. - // We need to move any overflow into the prebuffer. - - NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; - - if (overflow == 0) - { - // Perfect match! - // Every byte we read stays in the read buffer, - // and the last byte we read was the last byte of the term. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = YES; - } - else if (overflow > 0) - { - // The term was found within the data that we read, - // and there are extra bytes that extend past the end of the term. - // We need to move these excess bytes out of the read packet and into the prebuffer. - - NSInteger underflow = bytesRead - overflow; - - // Copy excess data into preBuffer - - LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow); - [preBuffer ensureCapacityForWrite:overflow]; - - uint8_t *overflowBuffer = buffer + underflow; - memcpy([preBuffer writeBuffer], overflowBuffer, overflow); - - [preBuffer didWrite:overflow]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Note: The completeCurrentRead method will trim the buffer for us. - - currentRead->bytesDone += underflow; - totalBytesReadForCurrentRead += underflow; - done = YES; - } - else - { - // The term was not found within the data that we read. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = NO; - } - } - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - - if (readIntoPreBuffer) - { - // We just read a chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - - // Now copy the data into the read packet. - // - // Recall that we didn't read directly into the packet's buffer to avoid - // over-allocating memory since we had no clue how much data was available to be read. - // - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesRead]; - - // Update totals - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - else - { - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - - done = YES; - } - - } // if (bytesRead > 0) - - } // if (!done && !error && !socketEOF && hasBytesAvailable) - - - if (!done && currentRead->readLength == 0 && currentRead->term == nil) - { - // Read type #1 - read all available data - // - // We might arrive here if we read data from the prebuffer but not from the socket. - - done = (totalBytesReadForCurrentRead > 0); - } - - // Check to see if we're done, or if we've made progress - - if (done) - { - [self completeCurrentRead]; - - if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) - { - [self maybeDequeueRead]; - } - } - else if (totalBytesReadForCurrentRead > 0) - { - // We're not done read type #2 or #3 yet, but we have read in some bytes - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) - { - long theReadTag = currentRead->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; - }}); - } - } - - // Check for errors - - if (error) - { - [self closeWithError:error]; - } - else if (socketEOF) - { - [self doReadEOF]; - } - else if (waiting) - { - if (![self usingCFStreamForTLS]) - { - // Monitor the socket for readability (if we're not already doing so) - [self resumeReadSource]; - } - } - - // Do not add any code here without first adding return statements in the error cases above. -} - -- (void)doReadEOF -{ - LogTrace(); - - // This method may be called more than once. - // If the EOF is read while there is still data in the preBuffer, - // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. - - flags |= kSocketHasReadEOF; - - if (flags & kSocketSecure) - { - // If the SSL layer has any buffered data, flush it into the preBuffer now. - - [self flushSSLBuffers]; - } - - BOOL shouldDisconnect = NO; - NSError *error = nil; - - if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) - { - // We received an EOF during or prior to startTLS. - // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. - - shouldDisconnect = YES; - - if ([self usingSecureTransportForTLS]) - { - error = [self sslError:errSSLClosedAbort]; - } - } - else if (flags & kReadStreamClosed) - { - // The preBuffer has already been drained. - // The config allows half-duplex connections. - // We've previously checked the socket, and it appeared writeable. - // So we marked the read stream as closed and notified the delegate. - // - // As per the half-duplex contract, the socket will be closed when a write fails, - // or when the socket is manually closed. - - shouldDisconnect = NO; - } - else if ([preBuffer availableBytes] > 0) - { - LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); - - // Although we won't be able to read any more data from the socket, - // there is existing data that has been prebuffered that we can read. - - shouldDisconnect = NO; - } - else if (config & kAllowHalfDuplexConnection) - { - // We just received an EOF (end of file) from the socket's read stream. - // This means the remote end of the socket (the peer we're connected to) - // has explicitly stated that it will not be sending us any more data. - // - // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) - - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - struct pollfd pfd[1]; - pfd[0].fd = socketFD; - pfd[0].events = POLLOUT; - pfd[0].revents = 0; - - poll(pfd, 1, 0); - - if (pfd[0].revents & POLLOUT) - { - // Socket appears to still be writeable - - shouldDisconnect = NO; - flags |= kReadStreamClosed; - - // Notify the delegate that we're going half-duplex - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidCloseReadStream:self]; - }}); - } - } - else - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - - - if (shouldDisconnect) - { - if (error == nil) - { - if ([self usingSecureTransportForTLS]) - { - if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) - { - error = [self sslError:sslErrCode]; - } - else - { - error = [self connectionClosedError]; - } - } - else - { - error = [self connectionClosedError]; - } - } - [self closeWithError:error]; - } - else - { - if (![self usingCFStreamForTLS]) - { - // Suspend the read source (if needed) - - [self suspendReadSource]; - } - } -} - -- (void)completeCurrentRead -{ - LogTrace(); - - NSAssert(currentRead, @"Trying to complete current read when there is no current read."); - - - NSData *result = nil; - - if (currentRead->bufferOwner) - { - // We created the buffer on behalf of the user. - // Trim our buffer to be the proper size. - [currentRead->buffer setLength:currentRead->bytesDone]; - - result = currentRead->buffer; - } - else - { - // We did NOT create the buffer. - // The buffer is owned by the caller. - // Only trim the buffer if we had to increase its size. - - if ([currentRead->buffer length] > currentRead->originalBufferLength) - { - NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; - NSUInteger origSize = currentRead->originalBufferLength; - - NSUInteger buffSize = MAX(readSize, origSize); - - [currentRead->buffer setLength:buffSize]; - } - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; - - result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; - } - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) - { - GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadData:result withTag:theRead->tag]; - }}); - } - - [self endCurrentRead]; -} - -- (void)endCurrentRead -{ - if (readTimer) - { - dispatch_source_cancel(readTimer); - readTimer = NULL; - } - - currentRead = nil; -} - -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doReadTimeout]; - - #pragma clang diagnostic pop - }}); - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadTimer = readTimer; - dispatch_source_set_cancel_handler(readTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(readTimer)"); - dispatch_release(theReadTimer); - - #pragma clang diagnostic pop - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(readTimer); - } -} - -- (void)doReadTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kReadsPaused; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - GCDAsyncReadPacket *theRead = currentRead; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag - elapsed:theRead->timeout - bytesDone:theRead->bytesDone]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doReadTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doReadTimeoutWithExtension:0.0]; - } -} - -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentRead) - { - if (timeoutExtension > 0.0) - { - currentRead->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause reads, and continue - flags &= ~kReadsPaused; - [self doReadData]; - } - else - { - LogVerbose(@"ReadTimeout"); - - [self closeWithError:[self readTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Writing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if ([data length] == 0) return; - - GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [writeQueue addObject:packet]; - [self maybeDequeueWrite]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!currentWrite || ![currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) - { - // We're not writing anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - NSUInteger done = currentWrite->bytesDone; - NSUInteger total = [currentWrite->buffer length]; - - if (tagPtr != NULL) *tagPtr = currentWrite->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - result = (float)done / (float)total; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * Conditionally starts a new write. - * - * It is called when: - * - a user requests a write - * - after a write request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueWrite -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - // If we're not currently processing a write AND we have an available write stream - if ((currentWrite == nil) && (flags & kConnected)) - { - if ([writeQueue count] > 0) - { - // Dequeue the next object in the write queue - currentWrite = [writeQueue objectAtIndex:0]; - [writeQueue removeObjectAtIndex:0]; - - - if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingWriteTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncWritePacket"); - - // Setup write timer (if needed) - [self setupWriteTimerWithTimeout:currentWrite->timeout]; - - // Immediately write, if possible - [self doWriteData]; - } - } - else if (flags & kDisconnectAfterWrites) - { - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - } -} - -- (void)doWriteData -{ - LogTrace(); - - // This method is called by the writeSource via the socketQueue - - if ((currentWrite == nil) || (flags & kWritesPaused)) - { - LogVerbose(@"No currentWrite or kWritesPaused"); - - // Unable to write at this time - - if ([self usingCFStreamForTLS]) - { - // CFWriteStream only fires once when there is available data. - // It won't fire again until we've invoked CFWriteStreamWrite. - } - else - { - // If the writeSource is firing, we need to pause it - // or else it will continue to fire over and over again. - - if (flags & kSocketCanAcceptBytes) - { - [self suspendWriteSource]; - } - } - return; - } - - if (!(flags & kSocketCanAcceptBytes)) - { - LogVerbose(@"No space available to write..."); - - // No space available to write. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - } - return; - } - - if (flags & kStartingWriteTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The writeQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingReadTLS) - { - if ([self usingSecureTransportForTLS]) - { - // We are in the process of a SSL Handshake. - // We were waiting for available space in the socket's internal OS buffer to continue writing. - - [self ssl_continueSSLHandshake]; - } - } - else - { - // We are still waiting for the readQueue to drain and start the SSL/TLS process. - // We now know we can write to the socket. - - if (![self usingCFStreamForTLS]) - { - // Suspend the write source or else it will continue to fire nonstop. - - [self suspendWriteSource]; - } - } - - return; - } - - // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) - - BOOL waiting = NO; - NSError *error = nil; - size_t bytesWritten = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // - // Writing data using CFStream (over internal TLS) - // - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite); - LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); - } - else - { - bytesWritten = (size_t)result; - - // We always set waiting to true in this scenario. - // CFStream may have altered our underlying socket to non-blocking. - // Thus if we attempt to write without a callback, we may end up blocking our queue. - waiting = YES; - } - - #endif - } - else - { - // We're going to use the SSLWrite function. - // - // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) - // - // Parameters: - // context - An SSL session context reference. - // data - A pointer to the buffer of data to write. - // dataLength - The amount, in bytes, of data to write. - // processed - On return, the length, in bytes, of the data actually written. - // - // It sounds pretty straight-forward, - // but there are a few caveats you should be aware of. - // - // The SSLWrite method operates in a non-obvious (and rather annoying) manner. - // According to the documentation: - // - // Because you may configure the underlying connection to operate in a non-blocking manner, - // a write operation might return errSSLWouldBlock, indicating that less data than requested - // was actually transferred. In this case, you should repeat the call to SSLWrite until some - // other result is returned. - // - // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, - // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), - // but it sets processed to dataLength !! - // - // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, - // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to - // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. - // - // You might be wondering: - // If the SSLWrite function doesn't tell us how many bytes were written, - // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) - // for the next time we invoke SSLWrite? - // - // The answer is that SSLWrite cached all the data we told it to write, - // and it will push out that data next time we call SSLWrite. - // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. - // If we call SSLWrite with empty data, then it will simply push out the cached data. - // - // For this purpose we're going to break large writes into a series of smaller writes. - // This allows us to report progress back to the delegate. - - OSStatus result; - - BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); - BOOL hasNewDataToWrite = YES; - - if (hasCachedDataToWrite) - { - size_t processed = 0; - - result = SSLWrite(sslContext, NULL, 0, &processed); - - if (result == noErr) - { - bytesWritten = sslWriteCachedLength; - sslWriteCachedLength = 0; - - if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) - { - // We've written all data for the current write. - hasNewDataToWrite = NO; - } - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - } - else - { - error = [self sslError:result]; - } - - // Can't write any new data since we were unable to write the cached data. - hasNewDataToWrite = NO; - } - } - - if (hasNewDataToWrite) - { - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] - + currentWrite->bytesDone - + bytesWritten; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - size_t bytesRemaining = bytesToWrite; - - BOOL keepLooping = YES; - while (keepLooping) - { - const size_t sslMaxBytesToWrite = 32768; - size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); - size_t sslBytesWritten = 0; - - result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten); - - if (result == noErr) - { - buffer += sslBytesWritten; - bytesWritten += sslBytesWritten; - bytesRemaining -= sslBytesWritten; - - keepLooping = (bytesRemaining > 0); - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - sslWriteCachedLength = sslBytesToWrite; - } - else - { - error = [self sslError:result]; - } - - keepLooping = NO; - } - - } // while (keepLooping) - - } // if (hasNewDataToWrite) - } - } - else - { - // - // Writing data directly over raw socket - // - - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); - LogVerbose(@"wrote to socket = %zd", result); - - // Check results - if (result < 0) - { - if (errno == EWOULDBLOCK) - { - waiting = YES; - } - else - { - error = [self errnoErrorWithReason:@"Error in write() function"]; - } - } - else - { - bytesWritten = result; - } - } - - // We're done with our writing. - // If we explictly ran into a situation where the socket told us there was no room in the buffer, - // then we immediately resume listening for notifications. - // - // We must do this before we dequeue another write, - // as that may in turn invoke this method again. - // - // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. - - if (waiting) - { - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - // Check our results - - BOOL done = NO; - - if (bytesWritten > 0) - { - // Update total amount read for the current write - currentWrite->bytesDone += bytesWritten; - LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone); - - // Is packet done? - done = (currentWrite->bytesDone == [currentWrite->buffer length]); - } - - if (done) - { - [self completeCurrentWrite]; - - if (!error) - { - dispatch_async(socketQueue, ^{ @autoreleasepool{ - - [self maybeDequeueWrite]; - }}); - } - } - else - { - // We were unable to finish writing the data, - // so we're waiting for another callback to notify us of available space in the lower-level output buffer. - - if (!waiting && !error) - { - // This would be the case if our write was able to accept some data, but not all of it. - - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - if (bytesWritten > 0) - { - // We're not done with the entire write, but we have written some bytes - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) - { - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; - }}); - } - } - } - - // Check for errors - - if (error) - { - [self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]]; - } - - // Do not add any code here without first adding a return statement in the error case above. -} - -- (void)completeCurrentWrite -{ - LogTrace(); - - NSAssert(currentWrite, @"Trying to complete current write when there is no current write."); - - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) - { - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWriteDataWithTag:theWriteTag]; - }}); - } - - [self endCurrentWrite]; -} - -- (void)endCurrentWrite -{ - if (writeTimer) - { - dispatch_source_cancel(writeTimer); - writeTimer = NULL; - } - - currentWrite = nil; -} - -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doWriteTimeout]; - - #pragma clang diagnostic pop - }}); - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theWriteTimer = writeTimer; - dispatch_source_set_cancel_handler(writeTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(writeTimer)"); - dispatch_release(theWriteTimer); - - #pragma clang diagnostic pop - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(writeTimer); - } -} - -- (void)doWriteTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kWritesPaused; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - GCDAsyncWritePacket *theWrite = currentWrite; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag - elapsed:theWrite->timeout - bytesDone:theWrite->bytesDone]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doWriteTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doWriteTimeoutWithExtension:0.0]; - } -} - -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentWrite) - { - if (timeoutExtension > 0.0) - { - currentWrite->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause writes, and continue - flags &= ~kWritesPaused; - [self doWriteData]; - } - else - { - LogVerbose(@"WriteTimeout"); - - [self closeWithError:[self writeTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)startTLS:(NSDictionary *)tlsSettings -{ - LogTrace(); - - if (tlsSettings == nil) - { - // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, - // but causes problems if we later try to fetch the remote host's certificate. - // - // To be exact, it causes the following to return NULL instead of the normal result: - // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) - // - // So we use an empty dictionary instead, which works perfectly. - - tlsSettings = [NSDictionary dictionary]; - } - - GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [writeQueue addObject:packet]; - - flags |= kQueuedTLS; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - }}); - -} - -- (void)maybeStartTLS -{ - // We can't start TLS until: - // - All queued reads prior to the user calling startTLS are complete - // - All queued writes prior to the user calling startTLS are complete - // - // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - BOOL useSecureTransport = YES; - - #if TARGET_OS_IPHONE - { - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; - if (value && [value boolValue] == YES) - useSecureTransport = NO; - } - #endif - - if (useSecureTransport) - { - [self ssl_startTLS]; - } - else - { - #if TARGET_OS_IPHONE - [self cf_startTLS]; - #endif - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via SecureTransport -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength -{ - LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength); - - if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) - { - LogVerbose(@"%@ - No data available to read...", THIS_METHOD); - - // No data available to read. - // - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t totalBytesRead = 0; - size_t totalBytesLeftToBeRead = *bufferLength; - - BOOL done = NO; - BOOL socketError = NO; - - // - // STEP 1 : READ FROM SSL PRE BUFFER - // - - size_t sslPreBufferLength = [sslPreBuffer availableBytes]; - - if (sslPreBufferLength > 0) - { - LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); - - size_t bytesToCopy; - if (sslPreBufferLength > totalBytesLeftToBeRead) - bytesToCopy = totalBytesLeftToBeRead; - else - bytesToCopy = sslPreBufferLength; - - LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy); - - memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - - // - // STEP 2 : READ FROM SOCKET - // - - if (!done && (socketFDBytesAvailable > 0)) - { - LogVerbose(@"%@: Reading from socket...", THIS_METHOD); - - int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD; - - BOOL readIntoPreBuffer; - size_t bytesToRead; - uint8_t *buf; - - if (socketFDBytesAvailable > totalBytesLeftToBeRead) - { - // Read all available data from socket into sslPreBuffer. - // Then copy requested amount into dataBuffer. - - LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); - - [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; - - readIntoPreBuffer = YES; - bytesToRead = (size_t)socketFDBytesAvailable; - buf = [sslPreBuffer writeBuffer]; - } - else - { - // Read available data from socket directly into dataBuffer. - - LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); - - readIntoPreBuffer = NO; - bytesToRead = totalBytesLeftToBeRead; - buf = (uint8_t *)buffer + totalBytesRead; - } - - ssize_t result = read(socketFD, buf, bytesToRead); - LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); - - if (result < 0) - { - LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); - - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - LogVerbose(@"%@: read EOF", THIS_METHOD); - - socketError = YES; - socketFDBytesAvailable = 0; - } - else - { - size_t bytesReadFromSocket = result; - - if (socketFDBytesAvailable > bytesReadFromSocket) - socketFDBytesAvailable -= bytesReadFromSocket; - else - socketFDBytesAvailable = 0; - - if (readIntoPreBuffer) - { - [sslPreBuffer didWrite:bytesReadFromSocket]; - - size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); - - LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy); - - memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - } - else - { - totalBytesRead += bytesReadFromSocket; - totalBytesLeftToBeRead -= bytesReadFromSocket; - } - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - } - - *bufferLength = totalBytesRead; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength -{ - if (!(flags & kSocketCanAcceptBytes)) - { - // Unable to write. - // - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t bytesToWrite = *bufferLength; - size_t bytesWritten = 0; - - BOOL done = NO; - BOOL socketError = NO; - - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - ssize_t result = write(socketFD, buffer, bytesToWrite); - - if (result < 0) - { - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - flags &= ~kSocketCanAcceptBytes; - } - else if (result == 0) - { - flags &= ~kSocketCanAcceptBytes; - } - else - { - bytesWritten = result; - - done = (bytesWritten == bytesToWrite); - } - - *bufferLength = bytesWritten; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); - - return [asyncSocket sslReadWithBuffer:data length:dataLength]; -} - -static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); - - return [asyncSocket sslWriteWithBuffer:data length:dataLength]; -} - -- (void)ssl_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via SecureTransport)..."); - - OSStatus status; - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - if (tlsPacket == nil) // Code to quiet the analyzer - { - NSAssert(NO, @"Logic error"); - - [self closeWithError:[self otherError:@"Logic error"]]; - return; - } - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - // Create SSLContext, and setup IO callbacks and connection ref - - BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue]; - - #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - { - if (isServer) - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); - else - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); - - if (sslContext == NULL) - { - [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; - return; - } - } - #else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - { - status = SSLNewContext(isServer, &sslContext); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; - return; - } - } - #endif - - status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; - return; - } - - status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; - return; - } - - - BOOL shouldManuallyEvaluateTrust = [[tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust] boolValue]; - if (shouldManuallyEvaluateTrust) - { - if (isServer) - { - [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; - return; - } - - status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; - return; - } - - #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - - // Note from Apple's documentation: - // - // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8. - // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the - // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus - // SSLSetEnableCertVerify is not available on that platform at all. - - status = SSLSetEnableCertVerify(sslContext, NO); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; - return; - } - - #endif - } - - // Configure SSLContext from given settings - // - // Checklist: - // 1. kCFStreamSSLPeerName - // 2. kCFStreamSSLCertificates - // 3. GCDAsyncSocketSSLPeerID - // 4. GCDAsyncSocketSSLProtocolVersionMin - // 5. GCDAsyncSocketSSLProtocolVersionMax - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - // 8. GCDAsyncSocketSSLCipherSuites - // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) - // - // Deprecated (throw error): - // 10. kCFStreamSSLAllowsAnyRoot - // 11. kCFStreamSSLAllowsExpiredRoots - // 12. kCFStreamSSLAllowsExpiredCertificates - // 13. kCFStreamSSLValidatesCertificateChain - // 14. kCFStreamSSLLevel - - id value; - - // 1. kCFStreamSSLPeerName - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName]; - if ([value isKindOfClass:[NSString class]]) - { - NSString *peerName = (NSString *)value; - - const char *peer = [peerName UTF8String]; - size_t peerLen = strlen(peer); - - status = SSLSetPeerDomainName(sslContext, peer, peerLen); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; - return; - } - - // 2. kCFStreamSSLCertificates - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates]; - if ([value isKindOfClass:[NSArray class]]) - { - CFArrayRef certs = (__bridge CFArrayRef)value; - - status = SSLSetCertificate(sslContext, certs); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; - return; - } - - // 3. GCDAsyncSocketSSLPeerID - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *peerIdData = (NSData *)value; - - status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." - @" (You can convert strings to data using a method like" - @" [string dataUsingEncoding:NSUTF8StringEncoding])"); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; - return; - } - - // 4. GCDAsyncSocketSSLProtocolVersionMin - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (minProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMin(sslContext, minProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; - return; - } - - // 5. GCDAsyncSocketSSLProtocolVersionMax - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (maxProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMax(sslContext, maxProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; - return; - } - - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; - if ([value isKindOfClass:[NSNumber class]]) - { - status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [value boolValue]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; - return; - } - - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; - if ([value isKindOfClass:[NSNumber class]]) - { - status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [value boolValue]); - if (status != noErr) - { - [self closeWithError: - [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." - @" Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; - return; - } - - // 8. GCDAsyncSocketSSLCipherSuites - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if ([value isKindOfClass:[NSArray class]]) - { - NSArray *cipherSuites = (NSArray *)value; - NSUInteger numberCiphers = [cipherSuites count]; - SSLCipherSuite ciphers[numberCiphers]; - - NSUInteger cipherIndex; - for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) - { - NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; - ciphers[cipherIndex] = [cipherObject shortValue]; - } - - status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; - return; - } - - // 9. GCDAsyncSocketSSLDiffieHellmanParameters - - #if !TARGET_OS_IPHONE - value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *diffieHellmanData = (NSData *)value; - - status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; - return; - } - #endif - - // DEPRECATED checks - - // 10. kCFStreamSSLAllowsAnyRoot - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; - return; - } - - // 11. kCFStreamSSLAllowsExpiredRoots - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; - return; - } - - // 12. kCFStreamSSLValidatesCertificateChain - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; - return; - } - - // 13. kCFStreamSSLAllowsExpiredCertificates - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; - return; - } - - // 14. kCFStreamSSLLevel - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" - @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; - return; - } - - // Setup the sslPreBuffer - // - // Any data in the preBuffer needs to be moved into the sslPreBuffer, - // as this data is now part of the secure read stream. - - sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - - size_t preBufferLength = [preBuffer availableBytes]; - - if (preBufferLength > 0) - { - [sslPreBuffer ensureCapacityForWrite:preBufferLength]; - - memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); - [preBuffer didRead:preBufferLength]; - [sslPreBuffer didWrite:preBufferLength]; - } - - sslErrCode = noErr; - - // Start the SSL Handshake process - - [self ssl_continueSSLHandshake]; -} - -- (void)ssl_continueSSLHandshake -{ - LogTrace(); - - // If the return value is noErr, the session is ready for normal secure communication. - // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. - // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the - // server and then call SSLHandshake again to resume the handshake or close the connection - // errSSLPeerBadCert SSL error. - // Otherwise, the return value indicates an error code. - - OSStatus status = SSLHandshake(sslContext); - - if (status == noErr) - { - LogVerbose(@"SSLHandshake complete"); - - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - else if (status == errSSLPeerAuthCompleted) - { - LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); - - __block SecTrustRef trust = NULL; - status = SSLCopyPeerTrust(sslContext, &trust); - if (status != noErr) - { - [self closeWithError:[self sslError:status]]; - return; - } - - int aStateIndex = stateIndex; - dispatch_queue_t theSocketQueue = socketQueue; - - __weak GCDAsyncSocket *weakSelf = self; - - void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - dispatch_async(theSocketQueue, ^{ @autoreleasepool { - - if (trust) { - CFRelease(trust); - trust = NULL; - } - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf) - { - [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; - } - }}); - - #pragma clang diagnostic pop - }}; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; - }}); - } - else - { - if (trust) { - CFRelease(trust); - trust = NULL; - } - - NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," - @" but delegate doesn't implement socket:shouldTrustPeer:"; - - [self closeWithError:[self otherError:msg]]; - return; - } - } - else if (status == errSSLWouldBlock) - { - LogVerbose(@"SSLHandshake continues..."); - - // Handshake continues... - // - // This method will be called again from doReadData or doWriteData. - } - else - { - [self closeWithError:[self sslError:status]]; - } -} - -- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex -{ - LogTrace(); - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); - - // One of the following is true - // - the socket was disconnected - // - the startTLS operation timed out - // - the completionHandler was already invoked once - - return; - } - - // Increment stateIndex to ensure completionHandler can only be called once. - stateIndex++; - - if (shouldTrust) - { - [self ssl_continueSSLHandshake]; - } - else - { - [self closeWithError:[self sslError:errSSLPeerBadCert]]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -- (void)cf_finishSSLHandshake -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } -} - -- (void)cf_abortSSLHandshake:(NSError *)error -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - [self closeWithError:error]; - } -} - -- (void)cf_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via CFStream)..."); - - if ([preBuffer availableBytes] > 0) - { - NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - [self suspendReadSource]; - [self suspendWriteSource]; - - socketFDBytesAvailable = 0; - flags &= ~kSocketCanAcceptBytes; - flags &= ~kSecureSocketHasBytesAvailable; - - flags |= kUsingCFStreamForTLS; - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:YES]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS"); - NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS"); - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; - - // Getting an error concerning kCFStreamPropertySSLSettings ? - // You need to add the CFNetwork framework to your iOS application. - - BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings); - BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings); - - // For some reason, starting around the time of iOS 4.3, - // the first call to set the kCFStreamPropertySSLSettings will return true, - // but the second will return false. - // - // Order doesn't seem to matter. - // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. - // Either way, the first call will return true, and the second returns false. - // - // Interestingly, this doesn't seem to affect anything. - // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) - // setting it on one side of the stream automatically sets it for the other side of the stream. - // - // Although there isn't anything in the documentation to suggest that the second attempt would fail. - // - // Furthermore, this only seems to affect streams that are negotiating a security upgrade. - // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure - // connection, and then a startTLS is issued. - // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). - - if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. - { - [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; - return; - } - - LogVerbose(@"Waiting for SSL Handshake to complete..."); -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -+ (void)ignore:(id)_ -{} - -+ (void)startCFStreamThreadIfNeeded -{ - LogTrace(); - - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - - cfstreamThreadRetainCount = 0; - cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL); - }); - - dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool { - - if (++cfstreamThreadRetainCount == 1) - { - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread) - object:nil]; - [cfstreamThread start]; - } - }}); -} - -+ (void)stopCFStreamThreadIfNeeded -{ - LogTrace(); - - // The creation of the cfstreamThread is relatively expensive. - // So we'd like to keep it available for recycling. - // However, there's a tradeoff here, because it shouldn't remain alive forever. - // So what we're going to do is use a little delay before taking it down. - // This way it can be reused properly in situations where multiple sockets are continually in flux. - - int delayInSeconds = 30; - dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - if (cfstreamThreadRetainCount == 0) - { - LogWarn(@"Logic error concerning cfstreamThread start / stop"); - return_from_block; - } - - if (--cfstreamThreadRetainCount == 0) - { - [cfstreamThread cancel]; // set isCancelled flag - - // wake up the thread - [GCDAsyncSocket performSelector:@selector(ignore:) - onThread:cfstreamThread - withObject:[NSNull null] - waitUntilDone:NO]; - - cfstreamThread = nil; - } - - #pragma clang diagnostic pop - }}); -} - -+ (void)cfstreamThread { @autoreleasepool -{ - [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; - - LogInfo(@"CFStreamThread: Started"); - - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(ignore:) - userInfo:nil - repeats:YES]; - - NSThread *currentThread = [NSThread currentThread]; - NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; - - BOOL isCancelled = [currentThread isCancelled]; - - while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) - { - isCancelled = [currentThread isCancelled]; - } - - LogInfo(@"CFStreamThread: Stopped"); -}} - -+ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); -} - -+ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); -} - -static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - - switch(type) - { - case kCFStreamEventHasBytesAvailable: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket doReadData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - Other"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } - -} - -static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - - switch(type) - { - case kCFStreamEventCanAcceptBytes: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket doWriteData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - Other"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } - -} - -- (BOOL)createReadAndWriteStream -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (readStream || writeStream) - { - // Streams already created - return YES; - } - - int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD; - - if (socketFD == SOCKET_NULL) - { - // Cannot create streams without a file descriptor - return NO; - } - - if (![self isConnected]) - { - // Cannot create streams until file descriptor is connected - return NO; - } - - LogVerbose(@"Creating read and write stream..."); - - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream); - - // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). - // But let's not take any chances. - - if (readStream) - CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - if (writeStream) - CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - - if ((readStream == NULL) || (writeStream == NULL)) - { - LogWarn(@"Unable to create read and write stream..."); - - if (readStream) - { - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - - return NO; - } - - return YES; -} - -- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite -{ - LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - streamContext.version = 0; - streamContext.info = (__bridge void *)(self); - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - readStreamEvents |= kCFStreamEventHasBytesAvailable; - - if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)) - { - return NO; - } - - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - writeStreamEvents |= kCFStreamEventCanAcceptBytes; - - if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext)) - { - return NO; - } - - return YES; -} - -- (BOOL)addStreamsToRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - if (!(flags & kAddedStreamsToRunLoop)) - { - LogVerbose(@"Adding streams to runloop..."); - - [[self class] startCFStreamThreadIfNeeded]; - [[self class] performSelector:@selector(scheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - - flags |= kAddedStreamsToRunLoop; - } - - return YES; -} - -- (void)removeStreamsFromRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - if (flags & kAddedStreamsToRunLoop) - { - LogVerbose(@"Removing streams from runloop..."); - - [[self class] performSelector:@selector(unscheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - [[self class] stopCFStreamThreadIfNeeded]; - - flags &= ~kAddedStreamsToRunLoop; - } -} - -- (BOOL)openStreams -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); - CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); - - if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) - { - LogVerbose(@"Opening read and write stream..."); - - BOOL r1 = CFReadStreamOpen(readStream); - BOOL r2 = CFWriteStreamOpen(writeStream); - - if (!r1 || !r2) - { - LogError(@"Error in CFStreamOpen"); - return NO; - } - } - - return YES; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * See header file for big discussion of this method. -**/ -- (BOOL)autoDisconnectOnClosedReadStream -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kAllowHalfDuplexConnection) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kAllowHalfDuplexConnection) == 0); - }); - - return result; - } -} - -/** - * See header file for big discussion of this method. -**/ -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kAllowHalfDuplexConnection; - else - config |= kAllowHalfDuplexConnection; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - - -/** - * See header file for big discussion of this method. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue -{ - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); -} - -/** - * See header file for big discussion of this method. -**/ -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue -{ - dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); -} - -/** - * See header file for big discussion of this method. -**/ -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socketFD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socket4FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - return socket4FD; -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socket6FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - return socket6FD; -} - -#if TARGET_OS_IPHONE - -/** - * Questions? Have you read the header file? -**/ -- (CFReadStreamRef)readStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - if (readStream == NULL) - [self createReadAndWriteStream]; - - return readStream; -} - -/** - * Questions? Have you read the header file? -**/ -- (CFWriteStreamRef)writeStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - if (writeStream == NULL) - [self createReadAndWriteStream]; - - return writeStream; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat -{ - if (![self createReadAndWriteStream]) - { - // Error occured creating streams (perhaps socket isn't open) - return NO; - } - - BOOL r1, r2; - - LogVerbose(@"Enabling backgrouding on socket"); - - r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - - if (!r1 || !r2) - { - return NO; - } - - if (!caveat) - { - if (![self openStreams]) - { - return NO; - } - } - - return YES; -} - -/** - * Questions? Have you read the header file? -**/ -- (BOOL)enableBackgroundingOnSocket -{ - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:NO]; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.??? -{ - // This method was created as a workaround for a bug in iOS. - // Apple has since fixed this bug. - // I'm not entirely sure which version of iOS they fixed it in... - - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:YES]; -} - -#endif - -- (SSLContextRef)sslContext -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - return sslContext; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - NSMutableArray *addresses = nil; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in nativeAddr4; - nativeAddr4.sin_len = sizeof(struct sockaddr_in); - nativeAddr4.sin_family = AF_INET; - nativeAddr4.sin_port = htons(port); - nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); - - struct sockaddr_in6 nativeAddr6; - nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); - nativeAddr6.sin6_family = AF_INET6; - nativeAddr6.sin6_port = htons(port); - nativeAddr6.sin6_flowinfo = 0; - nativeAddr6.sin6_addr = in6addr_loopback; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures - - NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - addresses = [NSMutableArray arrayWithCapacity:2]; - [addresses addObject:address4]; - [addresses addObject:address6]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - NSUInteger capacity = 0; - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { - capacity++; - } - } - - addresses = [NSMutableArray arrayWithCapacity:capacity]; - - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET) - { - // Found IPv4 address. - // Wrap the native address structure, and add to results. - - NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address4]; - } - else if (res->ai_family == AF_INET6) - { - // Found IPv6 address. - // Wrap the native address structure, and add to results. - - NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address6]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - if (errPtr) *errPtr = error; - return addresses; -} - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -+ (NSString *)hostFromAddress:(NSData *)address -{ - NSString *host; - - if ([self getHost:&host port:NULL fromAddress:address]) - return host; - else - return nil; -} - -+ (uint16_t)portFromAddress:(NSData *)address -{ - uint16_t port; - - if ([self getHost:NULL port:&port fromAddress:address]) - return port; - else - return 0; -} - -+ (BOOL)isIPv4Address:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET) { - return YES; - } - } - - return NO; -} - -+ (BOOL)isIPv6Address:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET6) { - return YES; - } - } - - return NO; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address -{ - return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - struct sockaddr_in sockaddr4; - memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; - if (afPtr) *afPtr = AF_INET; - - return YES; - } - } - else if (sockaddrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - struct sockaddr_in6 sockaddr6; - memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; - if (afPtr) *afPtr = AF_INET6; - - return YES; - } - } - } - - return NO; -} - -+ (NSData *)CRLFData -{ - return [NSData dataWithBytes:"\x0D\x0A" length:2]; -} - -+ (NSData *)CRData -{ - return [NSData dataWithBytes:"\x0D" length:1]; -} - -+ (NSData *)LFData -{ - return [NSData dataWithBytes:"\x0A" length:1]; -} - -+ (NSData *)ZeroData -{ - return [NSData dataWithBytes:"" length:1]; -} - -@end diff --git a/Vendor/CocoaLumberjack/DDASLLogger.h b/Vendor/CocoaLumberjack/DDASLLogger.h deleted file mode 100755 index 2aaf4e3225..0000000000 --- a/Vendor/CocoaLumberjack/DDASLLogger.h +++ /dev/null @@ -1,41 +0,0 @@ -#import -#import - -#import "DDLog.h" - -/** - * Welcome to Cocoa Lumberjack! - * - * The project page has a wealth of documentation if you have any questions. - * https://github.com/CocoaLumberjack/CocoaLumberjack - * - * If you're new to the project you may wish to read the "Getting Started" wiki. - * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted - * - * - * This class provides a logger for the Apple System Log facility. - * - * As described in the "Getting Started" page, - * the traditional NSLog() function directs it's output to two places: - * - * - Apple System Log - * - StdErr (if stderr is a TTY) so log statements show up in Xcode console - * - * To duplicate NSLog() functionality you can simply add this logger and a tty logger. - * However, if you instead choose to use file logging (for faster performance), - * you may choose to use a file logger and a tty logger. -**/ - -@interface DDASLLogger : DDAbstractLogger -{ - aslclient client; -} - -+ (instancetype)sharedInstance; - -// Inherited from DDAbstractLogger - -// - (id )logFormatter; -// - (void)setLogFormatter:(id )formatter; - -@end diff --git a/Vendor/CocoaLumberjack/DDASLLogger.m b/Vendor/CocoaLumberjack/DDASLLogger.m deleted file mode 100755 index 90beff113c..0000000000 --- a/Vendor/CocoaLumberjack/DDASLLogger.m +++ /dev/null @@ -1,100 +0,0 @@ -#import "DDASLLogger.h" - -#import - -/** - * Welcome to Cocoa Lumberjack! - * - * The project page has a wealth of documentation if you have any questions. - * https://github.com/CocoaLumberjack/CocoaLumberjack - * - * If you're new to the project you may wish to read the "Getting Started" wiki. - * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted -**/ - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - - -@implementation DDASLLogger - -static DDASLLogger *sharedInstance; - -/** - * The runtime sends initialize to each class in a program exactly one time just before the class, - * or any class that inherits from it, is sent its first message from within the program. (Thus the - * method may never be invoked if the class is not used.) The runtime sends the initialize message to - * classes in a thread-safe manner. Superclasses receive this message before their subclasses. - * - * This method may also be called directly (assumably by accident), hence the safety mechanism. -**/ -+ (void)initialize -{ - static BOOL initialized = NO; - if (!initialized) - { - initialized = YES; - - sharedInstance = [[[self class] alloc] init]; - } -} - -+ (instancetype)sharedInstance -{ - return sharedInstance; -} - -- (id)init -{ - if (sharedInstance != nil) - { - return nil; - } - - if ((self = [super init])) - { - // A default asl client is provided for the main thread, - // but background threads need to create their own client. - - client = asl_open(NULL, "com.apple.console", 0); - } - return self; -} - -- (void)logMessage:(DDLogMessage *)logMessage -{ - NSString *logMsg = logMessage->logMsg; - - if (formatter) - { - logMsg = [formatter formatLogMessage:logMessage]; - } - - if (logMsg) - { - const char *msg = [logMsg UTF8String]; - - int aslLogLevel; - switch (logMessage->logFlag) - { - // Note: By default ASL will filter anything above level 5 (Notice). - // So our mappings shouldn't go above that level. - - case LOG_FLAG_ERROR : aslLogLevel = ASL_LEVEL_ALERT; break; - case LOG_FLAG_WARN : aslLogLevel = ASL_LEVEL_CRIT; break; - case LOG_FLAG_INFO : aslLogLevel = ASL_LEVEL_ERR; break; - case LOG_FLAG_DEBUG : aslLogLevel = ASL_LEVEL_WARNING; break; - default : aslLogLevel = ASL_LEVEL_NOTICE; break; - } - - asl_log(client, NULL, aslLogLevel, "%s", msg); - } -} - -- (NSString *)loggerName -{ - return @"cocoa.lumberjack.aslLogger"; -} - -@end diff --git a/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h b/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h deleted file mode 100755 index 4e0c33cdac..0000000000 --- a/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h +++ /dev/null @@ -1,102 +0,0 @@ -#import - -#import "DDLog.h" - -/** - * Welcome to Cocoa Lumberjack! - * - * The project page has a wealth of documentation if you have any questions. - * https://github.com/CocoaLumberjack/CocoaLumberjack - * - * If you're new to the project you may wish to read the "Getting Started" wiki. - * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted - * - * - * This class provides an abstract implementation of a database logger. - * - * That is, it provides the base implementation for a database logger to build atop of. - * All that is needed for a concrete database logger is to extend this class - * and override the methods in the implementation file that are prefixed with "db_". -**/ - -@interface DDAbstractDatabaseLogger : DDAbstractLogger { -@protected - NSUInteger saveThreshold; - NSTimeInterval saveInterval; - NSTimeInterval maxAge; - NSTimeInterval deleteInterval; - BOOL deleteOnEverySave; - - BOOL saveTimerSuspended; - NSUInteger unsavedCount; - dispatch_time_t unsavedTime; - dispatch_source_t saveTimer; - dispatch_time_t lastDeleteTime; - dispatch_source_t deleteTimer; -} - -/** - * Specifies how often to save the data to disk. - * Since saving is an expensive operation (disk io) it is not done after every log statement. - * These properties allow you to configure how/when the logger saves to disk. - * - * A save is done when either (whichever happens first): - * - * - The number of unsaved log entries reaches saveThreshold - * - The amount of time since the oldest unsaved log entry was created reaches saveInterval - * - * You can optionally disable the saveThreshold by setting it to zero. - * If you disable the saveThreshold you are entirely dependent on the saveInterval. - * - * You can optionally disable the saveInterval by setting it to zero (or a negative value). - * If you disable the saveInterval you are entirely dependent on the saveThreshold. - * - * It's not wise to disable both saveThreshold and saveInterval. - * - * The default saveThreshold is 500. - * The default saveInterval is 60 seconds. -**/ -@property (assign, readwrite) NSUInteger saveThreshold; -@property (assign, readwrite) NSTimeInterval saveInterval; - -/** - * It is likely you don't want the log entries to persist forever. - * Doing so would allow the database to grow infinitely large over time. - * - * The maxAge property provides a way to specify how old a log statement can get - * before it should get deleted from the database. - * - * The deleteInterval specifies how often to sweep for old log entries. - * Since deleting is an expensive operation (disk io) is is done on a fixed interval. - * - * An alternative to the deleteInterval is the deleteOnEverySave option. - * This specifies that old log entries should be deleted during every save operation. - * - * You can optionally disable the maxAge by setting it to zero (or a negative value). - * If you disable the maxAge then old log statements are not deleted. - * - * You can optionally disable the deleteInterval by setting it to zero (or a negative value). - * - * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. - * - * It's not wise to enable both deleteInterval and deleteOnEverySave. - * - * The default maxAge is 7 days. - * The default deleteInterval is 5 minutes. - * The default deleteOnEverySave is NO. -**/ -@property (assign, readwrite) NSTimeInterval maxAge; -@property (assign, readwrite) NSTimeInterval deleteInterval; -@property (assign, readwrite) BOOL deleteOnEverySave; - -/** - * Forces a save of any pending log entries (flushes log entries to disk). -**/ -- (void)savePendingLogEntries; - -/** - * Removes any log entries that are older than maxAge. -**/ -- (void)deleteOldLogEntries; - -@end diff --git a/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m b/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m deleted file mode 100755 index 1410f7a5e7..0000000000 --- a/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m +++ /dev/null @@ -1,727 +0,0 @@ -#import "DDAbstractDatabaseLogger.h" -#import - -/** - * Welcome to Cocoa Lumberjack! - * - * The project page has a wealth of documentation if you have any questions. - * https://github.com/CocoaLumberjack/CocoaLumberjack - * - * If you're new to the project you may wish to read the "Getting Started" wiki. - * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted -**/ - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -@interface DDAbstractDatabaseLogger () -- (void)destroySaveTimer; -- (void)destroyDeleteTimer; -@end - -#pragma mark - - -@implementation DDAbstractDatabaseLogger - -- (id)init -{ - if ((self = [super init])) - { - saveThreshold = 500; - saveInterval = 60; // 60 seconds - maxAge = (60 * 60 * 24 * 7); // 7 days - deleteInterval = (60 * 5); // 5 minutes - } - return self; -} - -- (void)dealloc -{ - [self destroySaveTimer]; - [self destroyDeleteTimer]; - -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Override Me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)db_log:(DDLogMessage *)logMessage -{ - // Override me and add your implementation. - // - // Return YES if an item was added to the buffer. - // Return NO if the logMessage was ignored. - - return NO; -} - -- (void)db_save -{ - // Override me and add your implementation. -} - -- (void)db_delete -{ - // Override me and add your implementation. -} - -- (void)db_saveAndDelete -{ - // Override me and add your implementation. -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Private API -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)performSaveAndSuspendSaveTimer -{ - if (unsavedCount > 0) - { - if (deleteOnEverySave) - [self db_saveAndDelete]; - else - [self db_save]; - } - - unsavedCount = 0; - unsavedTime = 0; - - if (saveTimer && !saveTimerSuspended) - { - dispatch_suspend(saveTimer); - saveTimerSuspended = YES; - } -} - -- (void)performDelete -{ - if (maxAge > 0.0) - { - [self db_delete]; - - lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Timers -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)destroySaveTimer -{ - if (saveTimer) - { - dispatch_source_cancel(saveTimer); - if (saveTimerSuspended) - { - // Must resume a timer before releasing it (or it will crash) - dispatch_resume(saveTimer); - saveTimerSuspended = NO; - } - #if !OS_OBJECT_USE_OBJC - dispatch_release(saveTimer); - #endif - saveTimer = NULL; - } -} - -- (void)updateAndResumeSaveTimer -{ - if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0)) - { - uint64_t interval = (uint64_t)(saveInterval * NSEC_PER_SEC); - dispatch_time_t startTime = dispatch_time(unsavedTime, interval); - - dispatch_source_set_timer(saveTimer, startTime, interval, 1.0); - - if (saveTimerSuspended) - { - dispatch_resume(saveTimer); - saveTimerSuspended = NO; - } - } -} - -- (void)createSuspendedSaveTimer -{ - if ((saveTimer == NULL) && (saveInterval > 0.0)) - { - saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); - - dispatch_source_set_event_handler(saveTimer, ^{ @autoreleasepool { - - [self performSaveAndSuspendSaveTimer]; - - }}); - - saveTimerSuspended = YES; - } -} - -- (void)destroyDeleteTimer -{ - if (deleteTimer) - { - dispatch_source_cancel(deleteTimer); - #if !OS_OBJECT_USE_OBJC - dispatch_release(deleteTimer); - #endif - deleteTimer = NULL; - } -} - -- (void)updateDeleteTimer -{ - if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) - { - uint64_t interval = (uint64_t)(deleteInterval * NSEC_PER_SEC); - dispatch_time_t startTime; - - if (lastDeleteTime > 0) - startTime = dispatch_time(lastDeleteTime, interval); - else - startTime = dispatch_time(DISPATCH_TIME_NOW, interval); - - dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0); - } -} - -- (void)createAndStartDeleteTimer -{ - if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) - { - deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); - - if (deleteTimer != NULL) { - dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool { - - [self performDelete]; - - }}); - - [self updateDeleteTimer]; - - if (deleteTimer != NULL) dispatch_resume(deleteTimer); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSUInteger)saveThreshold -{ - // The design of this method is taken from the DDAbstractLogger implementation. - // For extensive documentation please refer to the DDAbstractLogger implementation. - - // Note: The internal implementation MUST access the colorsEnabled variable directly, - // This method is designed explicitly for external access. - // - // Using "self." syntax to go through this method will cause immediate deadlock. - // This is the intended result. Fix it by accessing the ivar directly. - // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - __block NSUInteger result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = saveThreshold; - }); - }); - - return result; -} - -- (void)setSaveThreshold:(NSUInteger)threshold -{ - dispatch_block_t block = ^{ @autoreleasepool { - - if (saveThreshold != threshold) - { - saveThreshold = threshold; - - // Since the saveThreshold has changed, - // we check to see if the current unsavedCount has surpassed the new threshold. - // - // If it has, we immediately save the log. - - if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) - { - [self performSaveAndSuspendSaveTimer]; - } - } - }}; - - // The design of the setter logic below is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - - if ([self isOnInternalLoggerQueue]) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } -} - -- (NSTimeInterval)saveInterval -{ - // The design of this method is taken from the DDAbstractLogger implementation. - // For extensive documentation please refer to the DDAbstractLogger implementation. - - // Note: The internal implementation MUST access the colorsEnabled variable directly, - // This method is designed explicitly for external access. - // - // Using "self." syntax to go through this method will cause immediate deadlock. - // This is the intended result. Fix it by accessing the ivar directly. - // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - __block NSTimeInterval result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = saveInterval; - }); - }); - - return result; -} - -- (void)setSaveInterval:(NSTimeInterval)interval -{ - dispatch_block_t block = ^{ @autoreleasepool { - - // C99 recommended floating point comparison macro - // Read: isLessThanOrGreaterThan(floatA, floatB) - - if (/* saveInterval != interval */ islessgreater(saveInterval, interval)) - { - saveInterval = interval; - - // There are several cases we need to handle here. - // - // 1. If the saveInterval was previously enabled and it just got disabled, - // then we need to stop the saveTimer. (And we might as well release it.) - // - // 2. If the saveInterval was previously disabled and it just got enabled, - // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) - // - // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. - // - // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. - // (Plus we might need to do an immediate save.) - - if (saveInterval > 0.0) - { - if (saveTimer == NULL) - { - // Handles #2 - // - // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, - // if a save is needed the timer will fire immediately. - - [self createSuspendedSaveTimer]; - [self updateAndResumeSaveTimer]; - } - else - { - // Handles #3 - // Handles #4 - // - // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, - // if a save is needed the timer will fire immediately. - - [self updateAndResumeSaveTimer]; - } - } - else if (saveTimer) - { - // Handles #1 - - [self destroySaveTimer]; - } - } - }}; - - // The design of the setter logic below is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - - if ([self isOnInternalLoggerQueue]) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } -} - -- (NSTimeInterval)maxAge -{ - // The design of this method is taken from the DDAbstractLogger implementation. - // For extensive documentation please refer to the DDAbstractLogger implementation. - - // Note: The internal implementation MUST access the colorsEnabled variable directly, - // This method is designed explicitly for external access. - // - // Using "self." syntax to go through this method will cause immediate deadlock. - // This is the intended result. Fix it by accessing the ivar directly. - // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - __block NSTimeInterval result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = maxAge; - }); - }); - - return result; -} - -- (void)setMaxAge:(NSTimeInterval)interval -{ - dispatch_block_t block = ^{ @autoreleasepool { - - // C99 recommended floating point comparison macro - // Read: isLessThanOrGreaterThan(floatA, floatB) - - if (/* maxAge != interval */ islessgreater(maxAge, interval)) - { - NSTimeInterval oldMaxAge = maxAge; - NSTimeInterval newMaxAge = interval; - - maxAge = interval; - - // There are several cases we need to handle here. - // - // 1. If the maxAge was previously enabled and it just got disabled, - // then we need to stop the deleteTimer. (And we might as well release it.) - // - // 2. If the maxAge was previously disabled and it just got enabled, - // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) - // - // 3. If the maxAge was increased, - // then we don't need to do anything. - // - // 4. If the maxAge was decreased, - // then we should do an immediate delete. - - BOOL shouldDeleteNow = NO; - - if (oldMaxAge > 0.0) - { - if (newMaxAge <= 0.0) - { - // Handles #1 - - [self destroyDeleteTimer]; - } - else if (oldMaxAge > newMaxAge) - { - // Handles #4 - shouldDeleteNow = YES; - } - } - else if (newMaxAge > 0.0) - { - // Handles #2 - shouldDeleteNow = YES; - } - - if (shouldDeleteNow) - { - [self performDelete]; - - if (deleteTimer) - [self updateDeleteTimer]; - else - [self createAndStartDeleteTimer]; - } - } - }}; - - // The design of the setter logic below is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - - if ([self isOnInternalLoggerQueue]) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } -} - -- (NSTimeInterval)deleteInterval -{ - // The design of this method is taken from the DDAbstractLogger implementation. - // For extensive documentation please refer to the DDAbstractLogger implementation. - - // Note: The internal implementation MUST access the colorsEnabled variable directly, - // This method is designed explicitly for external access. - // - // Using "self." syntax to go through this method will cause immediate deadlock. - // This is the intended result. Fix it by accessing the ivar directly. - // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - __block NSTimeInterval result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = deleteInterval; - }); - }); - - return result; -} - -- (void)setDeleteInterval:(NSTimeInterval)interval -{ - dispatch_block_t block = ^{ @autoreleasepool { - - // C99 recommended floating point comparison macro - // Read: isLessThanOrGreaterThan(floatA, floatB) - - if (/* deleteInterval != interval */ islessgreater(deleteInterval, interval)) - { - deleteInterval = interval; - - // There are several cases we need to handle here. - // - // 1. If the deleteInterval was previously enabled and it just got disabled, - // then we need to stop the deleteTimer. (And we might as well release it.) - // - // 2. If the deleteInterval was previously disabled and it just got enabled, - // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) - // - // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. - // - // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. - // (Plus we might need to do an immediate delete.) - - if (deleteInterval > 0.0) - { - if (deleteTimer == NULL) - { - // Handles #2 - // - // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, - // if a delete is needed the timer will fire immediately. - - [self createAndStartDeleteTimer]; - } - else - { - // Handles #3 - // Handles #4 - // - // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, - // if a save is needed the timer will fire immediately. - - [self updateDeleteTimer]; - } - } - else if (deleteTimer) - { - // Handles #1 - - [self destroyDeleteTimer]; - } - } - }}; - - // The design of the setter logic below is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - - if ([self isOnInternalLoggerQueue]) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } -} - -- (BOOL)deleteOnEverySave -{ - // The design of this method is taken from the DDAbstractLogger implementation. - // For extensive documentation please refer to the DDAbstractLogger implementation. - - // Note: The internal implementation MUST access the colorsEnabled variable directly, - // This method is designed explicitly for external access. - // - // Using "self." syntax to go through this method will cause immediate deadlock. - // This is the intended result. Fix it by accessing the ivar directly. - // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - __block BOOL result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = deleteOnEverySave; - }); - }); - - return result; -} - -- (void)setDeleteOnEverySave:(BOOL)flag -{ - dispatch_block_t block = ^{ - - deleteOnEverySave = flag; - }; - - // The design of the setter logic below is taken from the DDAbstractLogger implementation. - // For documentation please refer to the DDAbstractLogger implementation. - - if ([self isOnInternalLoggerQueue]) - { - block(); - } - else - { - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Public API -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)savePendingLogEntries -{ - dispatch_block_t block = ^{ @autoreleasepool { - - [self performSaveAndSuspendSaveTimer]; - }}; - - if ([self isOnInternalLoggerQueue]) - block(); - else - dispatch_async(loggerQueue, block); -} - -- (void)deleteOldLogEntries -{ - dispatch_block_t block = ^{ @autoreleasepool { - - [self performDelete]; - }}; - - if ([self isOnInternalLoggerQueue]) - block(); - else - dispatch_async(loggerQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark DDLogger -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)didAddLogger -{ - // If you override me be sure to invoke [super didAddLogger]; - - [self createSuspendedSaveTimer]; - - [self createAndStartDeleteTimer]; -} - -- (void)willRemoveLogger -{ - // If you override me be sure to invoke [super willRemoveLogger]; - - [self performSaveAndSuspendSaveTimer]; - - [self destroySaveTimer]; - [self destroyDeleteTimer]; -} - -- (void)logMessage:(DDLogMessage *)logMessage -{ - if ([self db_log:logMessage]) - { - BOOL firstUnsavedEntry = (++unsavedCount == 1); - - if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) - { - [self performSaveAndSuspendSaveTimer]; - } - else if (firstUnsavedEntry) - { - unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); - [self updateAndResumeSaveTimer]; - } - } -} - -- (void)flush -{ - // This method is invoked by DDLog's flushLog method. - // - // It is called automatically when the application quits, - // or if the developer invokes DDLog's flushLog method prior to crashing or something. - - [self performSaveAndSuspendSaveTimer]; -} - -@end diff --git a/Vendor/CocoaLumberjack/DDFileLogger.h b/Vendor/CocoaLumberjack/DDFileLogger.h deleted file mode 100755 index e5f20dc58c..0000000000 --- a/Vendor/CocoaLumberjack/DDFileLogger.h +++ /dev/null @@ -1,369 +0,0 @@ -#import -#import "DDLog.h" - -@class DDLogFileInfo; - -/** - * Welcome to Cocoa Lumberjack! - * - * The project page has a wealth of documentation if you have any questions. - * https://github.com/CocoaLumberjack/CocoaLumberjack - * - * If you're new to the project you may wish to read the "Getting Started" wiki. - * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted - * - * - * This class provides a logger to write log statements to a file. -**/ - - -// Default configuration and safety/sanity values. -// -// maximumFileSize -> DEFAULT_LOG_MAX_FILE_SIZE -// rollingFrequency -> DEFAULT_LOG_ROLLING_FREQUENCY -// maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES -// -// You should carefully consider the proper configuration values for your application. - -#define DEFAULT_LOG_MAX_FILE_SIZE (1024 * 1024) // 1 MB -#define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24) // 24 Hours -#define DEFAULT_LOG_MAX_NUM_LOG_FILES (5) // 5 Files - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// The LogFileManager protocol is designed to allow you to control all aspects of your log files. -// -// The primary purpose of this is to allow you to do something with the log files after they have been rolled. -// Perhaps you want to compress them to save disk space. -// Perhaps you want to upload them to an FTP server. -// Perhaps you want to run some analytics on the file. -// -// A default LogFileManager is, of course, provided. -// The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. -// -// This protocol provides various methods to fetch the list of log files. -// -// There are two variants: sorted and unsorted. -// If sorting is not necessary, the unsorted variant is obviously faster. -// The sorted variant will return an array sorted by when the log files were created, -// with the most recently created log file at index 0, and the oldest log file at the end of the array. -// -// You can fetch only the log file paths (full path including name), log file names (name only), -// or an array of DDLogFileInfo objects. -// The DDLogFileInfo class is documented below, and provides a handy wrapper that -// gives you easy access to various file attributes such as the creation date or the file size. - -@protocol DDLogFileManager -@required - -// Public properties - -/** - * The maximum number of archived log files to keep on disk. - * For example, if this property is set to 3, - * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk. - * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted. - * - * You may optionally disable deleting old/rolled/archived log files by setting this property to zero. -**/ -@property (readwrite, assign) NSUInteger maximumNumberOfLogFiles; - -// Public methods - -- (NSString *)logsDirectory; - -- (NSArray *)unsortedLogFilePaths; -- (NSArray *)unsortedLogFileNames; -- (NSArray *)unsortedLogFileInfos; - -- (NSArray *)sortedLogFilePaths; -- (NSArray *)sortedLogFileNames; -- (NSArray *)sortedLogFileInfos; - -// Private methods (only to be used by DDFileLogger) - -- (NSString *)createNewLogFile; - -@optional - -// Notifications from DDFileLogger - -- (void)didArchiveLogFile:(NSString *)logFilePath; -- (void)didRollAndArchiveLogFile:(NSString *)logFilePath; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Default log file manager. - * - * All log files are placed inside the logsDirectory. - * If a specific logsDirectory isn't specified, the default directory is used. - * On Mac, this is in ~/Library/Logs/. - * On iPhone, this is in ~/Library/Caches/Logs. - * - * Log files are named "