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