Permalink
Browse files

Merge branch 'master' of github.com:robbiehanson/XMPPFramework

  • Loading branch information...
2 parents bbc947d + 28c3e77 commit b7ad73f15b67e9967cbdc82c78b09318b6cce738 @robbiehanson committed May 31, 2012
View
39 Authentication/Digest-MD5/XMPPDigestMD5Authentication.m
@@ -35,6 +35,16 @@ @interface XMPPDigestMD5Authentication ()
NSString *password;
}
+// The properties are hooks (primarily for testing)
+
+@property (nonatomic, strong) NSString *realm;
+@property (nonatomic, strong) NSString *nonce;
+@property (nonatomic, strong) NSString *qop;
+@property (nonatomic, strong) NSString *cnonce;
+@property (nonatomic, strong) NSString *digestURI;
+@property (nonatomic, strong) NSString *username;
+@property (nonatomic, strong) NSString *password;
+
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
- (NSString *)base64EncodedFullResponse;
@@ -51,6 +61,14 @@ + (NSString *)mechanismName
return @"DIGEST-MD5";
}
+@synthesize realm;
+@synthesize nonce;
+@synthesize qop;
+@synthesize cnonce;
+@synthesize digestURI;
+@synthesize username;
+@synthesize password;
+
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
{
if ((self = [super init]))
@@ -119,7 +137,9 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
else
digestURI = [NSString stringWithFormat:@"xmpp/%@", serverHostName];
- cnonce = [XMPPStream generateUUID];
+ if (cnonce == nil)
+ cnonce = [XMPPStream generateUUID];
+
username = [myJID user];
// Create and send challenge response element
@@ -234,22 +254,37 @@ - (NSString *)response
NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", username, realm, password];
NSString *HA2str = [NSString stringWithFormat:@"AUTHENTICATE:%@", digestURI];
+ XMPPLogVerbose(@"HA1str: %@", HA1str);
+ XMPPLogVerbose(@"HA2str: %@", HA2str);
+
NSData *HA1dataA = [[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest];
NSData *HA1dataB = [[NSString stringWithFormat:@":%@:%@", nonce, cnonce] dataUsingEncoding:NSUTF8StringEncoding];
+ XMPPLogVerbose(@"HA1dataA: %@", HA1dataA);
+ XMPPLogVerbose(@"HA1dataB: %@", HA1dataB);
+
NSMutableData *HA1data = [NSMutableData dataWithCapacity:([HA1dataA length] + [HA1dataB length])];
[HA1data appendData:HA1dataA];
[HA1data appendData:HA1dataB];
+ XMPPLogVerbose(@"HA1data: %@", HA1data);
+
NSString *HA1 = [[HA1data md5Digest] hexStringValue];
NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+ XMPPLogVerbose(@"HA1: %@", HA1);
+ XMPPLogVerbose(@"HA2: %@", HA2);
+
NSString *responseStr = [NSString stringWithFormat:@"%@:%@:00000001:%@:auth:%@",
HA1, nonce, cnonce, HA2];
+ XMPPLogVerbose(@"responseStr: %@", responseStr);
+
NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+ XMPPLogVerbose(@"response: %@", response);
+
return response;
}
@@ -266,7 +301,7 @@ - (NSString *)base64EncodedFullResponse
[buffer appendFormat:@"response=%@,", [self response]];
[buffer appendFormat:@"charset=utf-8"];
- XMPPLogSend(@"decoded response: %@", buffer);
+ XMPPLogVerbose(@"%@: Decoded response: %@", THIS_FILE, buffer);
NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
View
22 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m
@@ -22,17 +22,6 @@
static char facebookAppIdKey;
@interface XMPPXFacebookPlatformAuthentication ()
-
-- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
-- (NSString *)base64EncodedFullResponse;
-
-@end
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark -
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-@implementation XMPPXFacebookPlatformAuthentication
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
@@ -48,6 +37,17 @@ @implementation XMPPXFacebookPlatformAuthentication
NSString *method;
}
+- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
+- (NSString *)base64EncodedFullResponse;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPXFacebookPlatformAuthentication
+
+ (NSString *)mechanismName
{
return @"X-FACEBOOK-PLATFORM";
View
6 Core/XMPPIQ.h
@@ -27,6 +27,9 @@
+ (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;
/**
* Creates and returns a new XMPPIQ element.
@@ -37,6 +40,9 @@
- (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;
/**
* Returns the type attribute of the IQ.
View
30 Core/XMPPIQ.m
@@ -65,6 +65,21 @@ + (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)
return [[XMPPIQ alloc] initWithType:type to:jid elementID:eid child:childElement];
}
++ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid
+{
+ return [[XMPPIQ alloc] initWithType:type to:nil elementID:eid child:nil];
+}
+
++ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
+{
+ return [[XMPPIQ alloc] initWithType:type to:nil elementID:eid child:childElement];
+}
+
++ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement
+{
+ return [[XMPPIQ alloc] initWithType:type to:nil elementID:nil child:childElement];
+}
+
- (id)init
{
return [self initWithType:nil to:nil elementID:nil child:nil];
@@ -104,6 +119,21 @@ - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid
return self;
}
+- (id)initWithType:(NSString *)type elementID:(NSString *)eid
+{
+ return [self initWithType:type to:nil elementID:eid child:nil];
+}
+
+- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
+{
+ return [self initWithType:type to:nil elementID:eid child:childElement];
+}
+
+- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement
+{
+ return [self initWithType:type to:nil elementID:nil child:childElement];
+}
+
- (NSString *)type
{
return [[self attributeStringValueForName:@"type"] lowercaseString];
View
2 Core/XMPPLogging.h
@@ -63,7 +63,9 @@
// Global flag to enable/disable logging throughout the entire xmpp framework.
+#ifndef XMPP_LOGGING_ENABLED
#define XMPP_LOGGING_ENABLED 1
+#endif
// Define logging context for every log message coming from the XMPP framework.
// The logging context can be extracted from the DDLogMessage from within the logging framework.
View
50 Core/XMPPModule.m
@@ -49,42 +49,7 @@ - (id)initWithDispatchQueue:(dispatch_queue_t)queue
- (void)dealloc
{
- if (xmppStream)
- {
- // It is dangerous to rely on the dealloc method to deactivate a module.
- //
- // Why?
- // Because when a module is activated, it is added as a delegate to the xmpp stream.
- // In addition to this, the module may be added as a delegate to various other xmpp components.
- // As per usual, these delegate references do NOT retain this module.
- // This means that modules may get invoked after they are deallocated.
- //
- // Consider the following example:
- //
- // 1. Thread A: Module is created (alloc/init) (retainCount = 1)
- // 2. Thread A: Module is activated (retainCount = 1)
- // 3. Thread A: Module is released, and dealloc is called.
- // First [MyCustomModule dealloc] is invoked.
- // Then [XMPPModule dealloc] is invoked.
- // Only at this point is [XMPPModule deactivate] run.
- // Since the deactivate method is synchronous,
- // it blocks until the module is removed as a delegate from the stream and all other modules.
- // 4. Thread B: Invokes a delegate method on our module ([XMPPModule deactivate] is waiting on thread B).
- // 5. Thread A: The [XMPPModule deactivate] method returns, but the damage is done.
- // Thread B has asynchronously dispatched a delegate method set to run on our module.
- // 6. Thread A: The dealloc method completes, and our module is now deallocated.
- // 7. Thread C: The delegate method attempts to run on our module, which is deallocated,
- // the application crashes, the computer blows up, and a meteor hits your favorite restaurant.
-
- XMPPLogWarn(@"%@: Deallocating activated module. You should deactivate modules before their final release.",
- NSStringFromClass([self class]));
-
- [self deactivate];
- }
-
-
dispatch_release(moduleQueue);
-
}
/**
@@ -148,20 +113,7 @@ - (void)deactivate
- (dispatch_queue_t)moduleQueue
{
- if (dispatch_get_current_queue() == moduleQueue)
- {
- return moduleQueue;
- }
- else
- {
- __block dispatch_queue_t result;
-
- dispatch_sync(moduleQueue, ^{
- result = moduleQueue;
- });
-
- return result;
- }
+ return moduleQueue;
}
- (XMPPStream *)xmppStream
View
34 Core/XMPPStream.h
@@ -8,7 +8,6 @@
#endif
@class XMPPSRVResolver;
-@class DDList;
@class XMPPParser;
@class XMPPJID;
@class XMPPIQ;
@@ -75,8 +74,9 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
NSTimeInterval keepAliveInterval;
dispatch_source_t keepAliveTimer;
NSTimeInterval lastSendReceiveTime;
+ NSData *keepAliveData;
- DDList *registeredModules;
+ NSMutableArray *registeredModules;
NSMutableDictionary *autoDelegateDict;
XMPPSRVResolver *srvResolver;
@@ -212,6 +212,17 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
@property (readwrite, assign) NSTimeInterval keepAliveInterval;
/**
+ * The keep-alive mechanism sends whitespace which is ignored by the xmpp protocol.
+ * The default whitespace character is a space (' ').
+ *
+ * This can be changed, for whatever reason, to another whitespace character.
+ * Valid whitespace characters are space(' '), tab('\t') and newline('\n').
+ *
+ * If you attempt to set the character to any non-whitespace character, the attempt is ignored.
+**/
+@property (readwrite, assign) char keepAliveWhitespaceCharacter;
+
+/**
* Represents the last sent presence element concerning the presence of myJID on the server.
* In other words, it represents the presence as others see us.
*
@@ -319,18 +330,23 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
/**
* Disconnects from the remote host by closing the underlying TCP socket connection.
+ * The terminating </stream:stream> element is not sent to the server.
*
- * The disconnect method is synchronous.
+ * This method is synchronous.
* Meaning that the disconnect will happen immediately, even if there are pending elements yet to be sent.
- * The xmppStreamDidDisconnect:withError: method will be invoked before the disconnect method returns.
*
- * The disconnectAfterSending method is asynchronous.
- * The disconnect will happen after all pending elements have been sent.
- * Attempting to send elements after this method is called will not result in the elements getting sent.
- * The disconnectAfterSending method will return immediately,
- * and the xmppStreamDidDisconnect:withError: delegate method will be invoked at a later time.
+ * The xmppStreamDidDisconnect:withError: delegate method will immediately be dispatched onto the delegate queue.
**/
- (void)disconnect;
+
+/**
+ * Disconnects from the remote host by sending the terminating </stream:stream> element,
+ * and then closing the underlying TCP socket connection.
+ *
+ * This method is asynchronous.
+ * The disconnect will happen after all pending elements have been sent.
+ * Attempting to send elements after this method has been called will not work (the elements won't get sent).
+**/
- (void)disconnectAfterSending;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
View
235 Core/XMPPStream.m
@@ -68,7 +68,9 @@ @interface XMPPStream (PrivateAPI)
- (void)cleanup;
- (void)setIsSecure:(BOOL)flag;
- (void)setIsAuthenticated:(BOOL)flag;
-- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag;
+- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag;
+- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag;
+- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag;
- (void)startNegotiation;
- (void)sendOpeningNegotiation;
- (void)continueStartTLS:(NSMutableDictionary *)settings;
@@ -115,8 +117,9 @@ - (void)commonInit
hostPort = 5222;
keepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
+ keepAliveData = [@" " dataUsingEncoding:NSUTF8StringEncoding];
- registeredModules = [[DDList alloc] init];
+ registeredModules = [[NSMutableArray alloc] init];
autoDelegateDict = [[NSMutableDictionary alloc] init];
receipts = [[NSMutableArray alloc] init];
@@ -403,20 +406,18 @@ - (XMPPPresence *)myPresence
- (NSTimeInterval)keepAliveInterval
{
+ __block NSTimeInterval result = 0.0;
+
+ dispatch_block_t block = ^{
+ result = keepAliveInterval;
+ };
+
if (dispatch_get_current_queue() == xmppQueue)
- {
- return keepAliveInterval;
- }
+ block();
else
- {
- __block NSTimeInterval result;
-
- dispatch_sync(xmppQueue, ^{
- result = keepAliveInterval;
- });
-
- return result;
- }
+ dispatch_sync(xmppQueue, block);
+
+ return result;
}
- (void)setKeepAliveInterval:(NSTimeInterval)interval
@@ -440,6 +441,43 @@ - (void)setKeepAliveInterval:(NSTimeInterval)interval
dispatch_async(xmppQueue, block);
}
+- (char)keepAliveWhitespaceCharacter
+{
+ __block char keepAliveChar = ' ';
+
+ dispatch_block_t block = ^{
+
+ NSString *keepAliveString = [[NSString alloc] initWithData:keepAliveData encoding:NSUTF8StringEncoding];
+ if ([keepAliveString length] > 0)
+ {
+ keepAliveChar = (char)[keepAliveString characterAtIndex:0];
+ }
+ };
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_sync(xmppQueue, block);
+
+ return keepAliveChar;
+}
+
+- (void)setKeepAliveWhitespaceCharacter:(char)keepAliveChar
+{
+ dispatch_block_t block = ^{
+
+ if (keepAliveChar == ' ' || keepAliveChar == '\n' || keepAliveChar == '\t')
+ {
+ keepAliveData = [[NSString stringWithFormat:@"%c", keepAliveChar] dataUsingEncoding:NSUTF8StringEncoding];
+ }
+ };
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_async(xmppQueue, block);
+}
+
- (UInt64)numberOfBytesSent
{
if (dispatch_get_current_queue() == xmppQueue)
@@ -1101,6 +1139,13 @@ - (void)disconnectAfterSending
}
else
{
+ NSString *termStr = @"</stream:stream>";
+ NSData *termData = [termStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", termStr);
+ numberOfBytesSent += [termData length];
+
+ [asyncSocket writeData:termData withTimeout:TIMEOUT_XMPP_WRITE tag:TAG_XMPP_WRITE_STREAM];
[asyncSocket disconnectAfterWriting];
// Everthing will be handled in socketDidDisconnect:withError:
@@ -1512,10 +1557,14 @@ - (BOOL)authenticate:(id <XMPPSASLAuthentication>)inAuth error:(NSError **)errPt
*
* This method exists for backwards compatibility, and may disappear in future versions.
**/
-- (BOOL)authenticateWithPassword:(NSString *)password error:(NSError **)errPtr
+- (BOOL)authenticateWithPassword:(NSString *)inPassword error:(NSError **)errPtr
{
XMPPLogTrace();
+ // The given password parameter could be mutable
+ NSString *password = [inPassword copy];
+
+
__block BOOL result = YES;
__block NSError *err = nil;
@@ -1692,7 +1741,7 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag
// None of the delegates implement the method.
// Use a shortcut.
- [self continueSendElement:iq withTag:tag];
+ [self continueSendIQ:iq withTag:tag];
}
else
{
@@ -1720,8 +1769,9 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag
dispatch_async(xmppQueue, ^{ @autoreleasepool {
- [self continueSendElement:iq withTag:tag];
-
+ if (state == STATE_XMPP_CONNECTED) {
+ [self continueSendIQ:iq withTag:tag];
+ }
}});
}});
@@ -1744,7 +1794,7 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag
// None of the delegates implement the method.
// Use a shortcut.
- [self continueSendElement:message withTag:tag];
+ [self continueSendMessage:message withTag:tag];
}
else
{
@@ -1772,8 +1822,9 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag
dispatch_async(xmppQueue, ^{ @autoreleasepool {
- [self continueSendElement:message withTag:tag];
-
+ if (state == STATE_XMPP_CONNECTED) {
+ [self continueSendMessage:message withTag:tag];
+ }
}});
}});
@@ -1796,7 +1847,7 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag
// None of the delegates implement the method.
// Use a shortcut.
- [self continueSendElement:presence withTag:tag];
+ [self continueSendPresence:presence withTag:tag];
}
else
{
@@ -1824,60 +1875,98 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag
dispatch_async(xmppQueue, ^{ @autoreleasepool {
- [self continueSendElement:presence withTag:tag];
-
+ if (state == STATE_XMPP_CONNECTED) {
+ [self continueSendPresence:presence withTag:tag];
+ }
}});
}});
}
}
-- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag
+- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag
{
NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
- if (state == STATE_XMPP_CONNECTED)
+ NSString *outgoingStr = [iq compactXMLString];
+ NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", outgoingStr);
+ numberOfBytesSent += [outgoingData length];
+
+ [asyncSocket writeData:outgoingData
+ withTimeout:TIMEOUT_XMPP_WRITE
+ tag:tag];
+
+ [multicastDelegate xmppStream:self didSendIQ:iq];
+}
+
+- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag
+{
+ NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
+
+ NSString *outgoingStr = [message compactXMLString];
+ NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", outgoingStr);
+ numberOfBytesSent += [outgoingData length];
+
+ [asyncSocket writeData:outgoingData
+ withTimeout:TIMEOUT_XMPP_WRITE
+ tag:tag];
+
+ [multicastDelegate xmppStream:self didSendMessage:message];
+}
+
+- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag
+{
+ NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
+
+ NSString *outgoingStr = [presence compactXMLString];
+ NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", outgoingStr);
+ numberOfBytesSent += [outgoingData length];
+
+ [asyncSocket writeData:outgoingData
+ withTimeout:TIMEOUT_XMPP_WRITE
+ tag:tag];
+
+ // Update myPresence if this is a normal presence element.
+ // In other words, ignore presence subscription stuff, MUC room stuff, etc.
+ //
+ // We use the built-in [presence type] which guarantees lowercase strings,
+ // and will return @"available" if there was no set type (as available is implicit).
+
+ NSString *type = [presence type];
+ if ([type isEqualToString:@"available"] || [type isEqualToString:@"unavailable"])
{
- NSString *outgoingStr = [element compactXMLString];
- NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
-
- XMPPLogSend(@"SEND: %@", outgoingStr);
- numberOfBytesSent += [outgoingData length];
-
- [asyncSocket writeData:outgoingData
- withTimeout:TIMEOUT_XMPP_WRITE
- tag:tag];
-
- if ([element isKindOfClass:[XMPPIQ class]])
- {
- [multicastDelegate xmppStream:self didSendIQ:(XMPPIQ *)element];
- }
- else if ([element isKindOfClass:[XMPPMessage class]])
+ if ([presence toStr] == nil && myPresence != presence)
{
- [multicastDelegate xmppStream:self didSendMessage:(XMPPMessage *)element];
- }
- else if ([element isKindOfClass:[XMPPPresence class]])
- {
- // Update myPresence if this is a normal presence element.
- // In other words, ignore presence subscription stuff, MUC room stuff, etc.
-
- XMPPPresence *presence = (XMPPPresence *)element;
-
- // We use the built-in [presence type] which guarantees lowercase strings,
- // and will return @"available" if there was no set type (as available is implicit).
-
- NSString *type = [presence type];
- if ([type isEqualToString:@"available"] || [type isEqualToString:@"unavailable"])
- {
- if ([presence toStr] == nil && myPresence != presence)
- {
- myPresence = presence;
- }
- }
-
- [multicastDelegate xmppStream:self didSendPresence:(XMPPPresence *)element];
+ myPresence = presence;
}
}
+
+ [multicastDelegate xmppStream:self didSendPresence:presence];
+}
+
+- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag
+{
+ NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
+
+ NSString *outgoingStr = [element compactXMLString];
+ NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", outgoingStr);
+ numberOfBytesSent += [outgoingData length];
+
+ [asyncSocket writeData:outgoingData
+ withTimeout:TIMEOUT_XMPP_WRITE
+ tag:tag];
}
/**
@@ -1917,6 +2006,10 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag
{
[self sendPresence:[XMPPPresence presenceFromElement:element] withTag:tag];
}
+ else
+ {
+ [self continueSendElement:element withTag:tag];
+ }
}
}
@@ -3345,11 +3438,9 @@ - (void)keepAlive
if (elapsed < 0 || elapsed >= keepAliveInterval)
{
- NSData *outgoingData = [@" " dataUsingEncoding:NSUTF8StringEncoding];
-
- numberOfBytesSent += [outgoingData length];
+ numberOfBytesSent += [keepAliveData length];
- [asyncSocket writeData:outgoingData
+ [asyncSocket writeData:keepAliveData
withTimeout:TIMEOUT_XMPP_WRITE
tag:TAG_XMPP_WRITE_STREAM];
@@ -3377,7 +3468,7 @@ - (void)registerModule:(XMPPModule *)module
// Register module
- [registeredModules add:(__bridge void *)module];
+ [registeredModules addObject:module];
// Add auto delegates (if there are any)
@@ -3435,7 +3526,7 @@ - (void)unregisterModule:(XMPPModule *)module
// Unregister modules
- [registeredModules remove:(__bridge void *)module];
+ [registeredModules removeObject:module];
}};
@@ -3472,7 +3563,6 @@ - (void)autoAddDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQue
// It will be added as a delegate to future registered modules of the given class.
id delegates = [autoDelegateDict objectForKey:className];
-
if (delegates == nil)
{
delegates = [[GCDMulticastDelegate alloc] init];
@@ -3538,6 +3628,11 @@ - (void)removeAutoDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegate
GCDMulticastDelegate *delegates = [autoDelegateDict objectForKey:className];
[delegates removeDelegate:delegate delegateQueue:delegateQueue];
+
+ if ([delegates count] == 0)
+ {
+ [autoDelegateDict removeObjectForKey:className];
+ }
}
}};
View
229 Extensions/XEP-0016/XMPPPrivacy.h
@@ -0,0 +1,229 @@
+#import <Foundation/Foundation.h>
+#import "XMPPModule.h"
+
+#if TARGET_OS_IPHONE
+ #import "DDXML.h"
+#endif
+
+@class XMPPIQ;
+
+extern NSString *const XMPPPrivacyErrorDomain;
+
+typedef enum XMPPPrivacyErrorCode
+{
+ XMPPPrivacyQueryTimeout, // No response from server
+ XMPPPrivacyDisconnect, // XMPP disconnection
+
+} XMPPPrivacyErrorCode;
+
+
+
+@interface XMPPPrivacy : XMPPModule
+{
+ BOOL autoRetrievePrivacyListNames;
+ BOOL autoRetrievePrivacyListItems;
+ BOOL autoClearPrivacyListInfo;
+
+ NSMutableDictionary *privacyDict;
+ NSString *activeListName;
+ NSString *defaultListName;
+
+ NSMutableDictionary *pendingQueries;
+}
+
+/**
+ * Whether or not the module should automatically retrieve the privacy list names.
+ * If this property is enabled, then the list is automatically fetched when the client signs in.
+ *
+ * The default value is YES.
+**/
+@property (readwrite, assign) BOOL autoRetrievePrivacyListNames;
+
+/**
+ * Whether or not the module should automatically retrieve the privacy list rules.
+ * If this property is enabled, then the rules for each privacy list are automatically fetched.
+ *
+ * In other words, if the privacy list names are fetched (either automatically, or via retrieveListNames),
+ * then the module will automatically fetch the associated rules.
+ * It will also update the set of rules if we receive a "privacy list push"
+ * from the server that another resource has changed one of the privacy lists.
+ *
+ * The default value is YES.
+**/
+@property (readwrite, assign) BOOL autoRetrievePrivacyListItems;
+
+/**
+ * Whether the module should automatically clear the privacy list info when the client disconnects.
+ *
+ * As per the XEP, if there are multiple resources signed in for the user,
+ * and one resource makes changes to a privacy list, all other resources are "pushed" a notification.
+ * However, if our client is disconnected when another resource makes the changes,
+ * then the only way we can find out about the changes are to redownload the privacy lists.
+ *
+ * It is recommended to clear the privacy list to assure we have the correct info.
+ * However, there may be specific situations in which an xmpp client can be sure the privacy list won't change.
+ *
+ * The default value is YES.
+**/
+@property (readwrite, assign) BOOL autoClearPrivacyListInfo;
+
+/**
+ * Manual fetch of list names and rules, and manual control over when to clear stored info.
+**/
+- (void)retrieveListNames;
+- (void)retrieveListWithName:(NSString *)privacyListName;
+- (void)clearPrivacyListInfo;
+
+/**
+ * Returns the privacy list names.
+ * This is an array of strings.
+**/
+- (NSArray *)listNames;
+
+/**
+ * Returns the privacy list rules/items for the given list name.
+ *
+ * The result is an array or privacy items (NSXMLElement's).
+ * The array is sorted according to order, where the item with the smallest 'order' is first in the array.
+**/
+- (NSArray *)listWithName:(NSString *)privacyListName;
+
+/**
+ * Returns information about the active list.
+ * If there is no active list, the methods return nil.
+ *
+ * The activeList method is a convenience method for [modPriv listWithName:[modPriv activeListName]]
+**/
+- (NSString *)activeListName;
+- (NSArray *)activeList;
+
+/**
+ * Returns information about the default list.
+ * If there is no default list, the methods return nil.
+ *
+ * The defaultList method is a convenience method for [modPriv listWithName:[modPriv defaultListName]]
+**/
+- (NSString *)defaultListName;
+- (NSArray *)defaultList;
+
+/**
+ * Changes the client's active privacy list to the list with the given name.
+ * The privacy list name must match the name of an existing privacy list.
+ *
+ * To decline the use of an active list simply pass nil to this method.
+ *
+ * Once the server has acknowledged the change,
+ * the delegate method xmppPrivacy:didSetActiveListName: will be invoked.
+ * If the server is unable to process the change (e.g. invalid list name),
+ * the delegate method xmppPrivacy:didNotSetActiveListName:error: will be invoked.
+ *
+ * The methods activeListName and activeList will update after the server acknowledges the change.
+**/
+- (void)setActiveListName:(NSString *)privacyListName;
+
+/**
+ * Changes the client's default privacy list to the list with the given name.
+ * The privacy list name must match the name of an existing privacy list.
+ *
+ * To decline the use of a default list simply pass nil to this method.
+ *
+ * Once the server has acknowledged the change,
+ * the delegate method xmppPrivacy:didSetDefaultListName: will be invoked.
+ * If the server is unable to process the change (e.g. invalid list name, in use by another resource),
+ * the delegate method xmppPrivacy:didNotSetDefaultListName:error: will be invoked.
+ *
+ * The methods defaultListName and defaultList will update after the server acknowledges the change.
+**/
+- (void)setDefaultListName:(NSString *)privacyListName;
+
+/**
+ * Adds/Edits/Removes a privacy list with the given name.
+ * The given array should contain only privacy items (NSXMLElement's).
+ *
+ * To remove a privacy list, invoke this method will a nil or empty items parameter.
+**/
+- (void)setListWithName:(NSString *)privacyListName items:(NSArray *)items;
+
+/**
+ * The following are convenience methods to create privacy item rules.
+ * A quick refresher from the XEP-0016 documentation is provided below.
+ *
+ * The 'type' attribute is OPTIONAL, and must be one of: jid|group|subscription
+ *
+ * If the 'type' is 'jid', then the 'value' must contain a valid JID.
+ * JIDs are to be matched in the following order:
+ *
+ * - <user@domain/resource> (only that resource matches)
+ * - <user@domain> (any resource matches)
+ * - <domain/resource> (only that resource matches)
+ * - <domain> (the domain itself matches, as does any user@domain or domain/resource)
+ *
+ * If the 'type' is 'group', then the 'value' should contain the name of a group in the user's roster.
+ *
+ * If the 'type' is 'subscription', then the 'value' must be one of: both|to|from|none
+ *
+ * The 'action' attribute is MANDATORY and must be one of: allow|deny
+ *
+ * The 'order' attribute is MANDATORY and must be a non-negative integer that is unique among all items in the list.
+ * List items are processed by the server according to the 'order' attribute in ascending order. (0 before 1, etc)
+ * Once the server matches a privacy item in the list, it obeys the item and ceases processing.
+ *
+ * The privacy item may contain one or more child elements that specify more granular blocking control:
+ *
+ * - <message/> (blocks incoming message stanzas)
+ * - <iq/> (blocks incoming IQ stanzas)
+ * - <presence-in/> (blocks incoming presence notifications)
+ * - <presence-out/> (blocks outgoing presence notifications)
+**/
++ (NSXMLElement *)privacyItemWithAction:(NSString *)action order:(NSUInteger)order;
++ (NSXMLElement *)privacyItemWithType:(NSString *)type
+ value:(NSString *)value
+ action:(NSString *)action
+ order:(NSUInteger)order;
+
++ (void)blockIQs:(NSXMLElement *)privacyItem;
++ (void)blockMessages:(NSXMLElement *)privacyItem;
++ (void)blockPresenceIn:(NSXMLElement *)privacyItem;
++ (void)blockPresenceOut:(NSXMLElement *)privacyItem;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol XMPPPrivacyDelegate
+@optional
+
+/**
+ * The following delegate methods correspond almost exactly with the action methods of the class.
+ * There are a few possible ways in which an action could fail:
+ *
+ * 1. We receive an error response from the server.
+ * 2. We receive no response from the server, and the query times out.
+ * 3. We get disconnected before we receive the response.
+ *
+ * In case number 1, the error will be an XMPPIQ of type='error'.
+ *
+ * In case number 2 or 3, the error will be an NSError
+ * with domain=XMPPPrivacyErrorDomain and code from the XMPPPrivacyErrorCode enumeration.
+**/
+
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didReceiveListNames:(NSArray *)listNames;
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotReceiveListNamesDueToError:(id)error;
+
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didReceiveListWithName:(NSString *)name items:(NSArray *)items;
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotReceiveListWithName:(NSString *)name error:(id)error;
+
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didReceivePushWithListName:(NSString *)name;
+
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didSetActiveListName:(NSString *)name;
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotSetActiveListName:(NSString *)name error:(id)error;
+
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didSetDefaultListName:(NSString *)name;
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotSetDefaultListName:(NSString *)name error:(id)error;
+
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didSetListWithName:(NSString *)name;
+- (void)xmppPrivacy:(XMPPPrivacy *)sender didNotSetListWithName:(NSString *)name error:(id)error;
+
+@end
View
1,009 Extensions/XEP-0016/XMPPPrivacy.m
@@ -0,0 +1,1009 @@
+#import "XMPP.h"
+#import "XMPPLogging.h"
+#import "XMPPPrivacy.h"
+
+// Log levels: off, error, warn, info, verbose
+// Log flags: trace
+#ifdef DEBUG
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE; // | XMPP_LOG_FLAG_TRACE;
+#else
+ static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
+#endif
+
+#define QUERY_TIMEOUT 30.0 // NSTimeInterval (double) = seconds
+
+NSString *const XMPPPrivacyErrorDomain = @"XMPPPrivacyErrorDomain";
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+typedef enum XMPPPrivacyQueryInfoType {
+ FetchList,
+ FetchRules,
+ EditList,
+ SetActiveList,
+ SetDefaultList
+
+} XMPPPrivacyQueryInfoType;
+
+@interface XMPPPrivacyQueryInfo : NSObject
+{
+ XMPPPrivacyQueryInfoType type;
+ NSString *privacyListName;
+ NSArray *privacyListItems;
+
+ dispatch_source_t timer;
+}
+
+@property (nonatomic, readonly) XMPPPrivacyQueryInfoType type;
+@property (nonatomic, readonly) NSString *privacyListName;
+@property (nonatomic, readonly) NSArray *privacyListItems;
+
+@property (nonatomic, readwrite, retain) dispatch_source_t timer;
+
++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type;
++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name;
++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name items:(NSArray *)items;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface XMPPPrivacy (/* Must be nameless for properties */)
+
+- (void)addQueryInfo:(XMPPPrivacyQueryInfo *)qi withKey:(NSString *)uuid;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPPrivacy
+
+- (id)init
+{
+ return [self initWithDispatchQueue:NULL];
+}
+
+- (id)initWithDispatchQueue:(dispatch_queue_t)queue
+{
+ if ((self = [super initWithDispatchQueue:queue]))
+ {
+ autoRetrievePrivacyListNames = YES;
+ autoRetrievePrivacyListItems = YES;
+ autoClearPrivacyListInfo = YES;
+
+ privacyDict = [[NSMutableDictionary alloc] init];
+ activeListName = nil;
+ defaultListName = nil;
+
+ pendingQueries = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (BOOL)activate:(XMPPStream *)aXmppStream
+{
+ if ([super activate:aXmppStream])
+ {
+ // Reserved for possible future use.
+
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void)deactivate
+{
+ // Reserved for possible future use.
+
+ [super deactivate];
+}
+
+- (void)dealloc
+{
+ [privacyDict release];
+ [activeListName release];
+ [defaultListName release];
+ [pendingQueries release];
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Properties
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)autoRetrievePrivacyListNames
+{
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ return autoRetrievePrivacyListNames;
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(moduleQueue, ^{
+ result = autoRetrievePrivacyListNames;
+ });
+
+ return result;
+ }
+}
+
+- (void)setAutoRetrievePrivacyListNames:(BOOL)flag
+{
+ dispatch_block_t block = ^{
+
+ autoRetrievePrivacyListNames = flag;
+ };
+
+ if (dispatch_get_current_queue() == moduleQueue)
+ block();
+ else
+ dispatch_async(moduleQueue, block);
+}
+
+- (BOOL)autoRetrievePrivacyListItems
+{
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ return autoRetrievePrivacyListItems;
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(moduleQueue, ^{
+ result = autoRetrievePrivacyListItems;
+ });
+
+ return result;
+ }
+}
+
+- (void)setAutoRetrievePrivacyListItems:(BOOL)flag
+{
+ dispatch_block_t block = ^{
+
+ autoRetrievePrivacyListItems = flag;
+ };
+
+ if (dispatch_get_current_queue() == moduleQueue)
+ block();
+ else
+ dispatch_async(moduleQueue, block);
+}
+
+- (BOOL)autoClearPrivacyListInfo
+{
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ return autoClearPrivacyListInfo;
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(moduleQueue, ^{
+ result = autoClearPrivacyListInfo;
+ });
+
+ return result;
+ }
+}
+
+- (void)setAutoClearPrivacyListInfo:(BOOL)flag
+{
+ dispatch_block_t block = ^{
+
+ autoClearPrivacyListInfo = flag;
+ };
+
+ if (dispatch_get_current_queue() == moduleQueue)
+ block();
+ else
+ dispatch_async(moduleQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Public API
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)retrieveListNames
+{
+ XMPPLogTrace();
+
+ // <iq type='get' id='abc123'>
+ // <query xmlns='jabber:iq:privacy'/>
+ // </iq>
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
+
+ NSString *uuid = [xmppStream generateUUID];
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:query];
+
+ [xmppStream sendElement:iq];
+
+ XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:FetchList];
+ [self addQueryInfo:qi withKey:uuid];
+}
+
+- (void)retrieveListWithName:(NSString *)privacyListName
+{
+ XMPPLogTrace();
+
+ if (privacyListName == nil) return;
+
+ // <iq type='get' id='abc123'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <list name='public'/>
+ // </query>
+ // </iq>
+
+ NSXMLElement *list = [NSXMLElement elementWithName:@"list"];
+ [list addAttributeWithName:@"name" stringValue:privacyListName];
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
+ [query addChild:list];
+
+ NSString *uuid = [xmppStream generateUUID];
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:uuid child:query];
+
+ [xmppStream sendElement:iq];
+
+ XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:FetchRules name:privacyListName];
+ [self addQueryInfo:qi withKey:uuid];
+}
+
+- (void)clearPrivacyListInfo
+{
+ XMPPLogTrace();
+
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ [privacyDict removeAllObjects];
+ }
+ else
+ {
+ dispatch_async(moduleQueue, ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [privacyDict removeAllObjects];
+
+ [pool drain];
+ });
+ }
+}
+
+- (NSArray *)listNames
+{
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ return [privacyDict allKeys];
+ }
+ else
+ {
+ __block NSArray *result;
+
+ dispatch_sync(moduleQueue, ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ result = [[privacyDict allKeys] copy];
+
+ [pool drain];
+ });
+
+ return [result autorelease];
+ }
+}
+
+- (NSArray *)listWithName:(NSString *)privacyListName
+{
+ NSArray* (^block)() = ^ NSArray* () {
+
+ id result = [privacyDict objectForKey:privacyListName];
+
+ if (result == [NSNull null]) // Not fetched yet
+ return nil;
+ else
+ return (NSArray *)result;
+ };
+
+ // ExecuteVoidBlock(moduleQueue, block);
+ // ExecuteNonVoidBlock(moduleQueue, block, NSArray*)
+
+ int n = MIN(5, 6);
+
+ if (dispatch_get_current_queue() == moduleQueue)
+ {
+ return block();
+ }
+ else
+ {
+ __block NSArray *result;
+
+ dispatch_sync(moduleQueue, ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ result = [block() retain];
+
+ [pool drain];
+ });
+
+ return [result autorelease];
+ }
+}
+
+- (NSString *)activeListName
+{
+ return activeListName;
+}
+
+- (NSArray *)activeList
+{
+ return [self listWithName:activeListName];
+}
+
+- (void)setActiveListName:(NSString *)privacyListName
+{
+ // Setting active list:
+ //
+ // <iq type='set' id='active1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <active name='special'/>
+ // </query>
+ // </iq>
+ //
+ // Decline the use of active lists:
+ //
+ // <iq type='set' id='active3'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <active/>
+ // </query>
+ // </iq>
+
+ NSXMLElement *active = [NSXMLElement elementWithName:@"active"];
+ if (privacyListName)
+ {
+ [active addAttributeWithName:@"name" stringValue:privacyListName];
+ }
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
+ [query addChild:active];
+
+ NSString *uuid = [xmppStream generateUUID];
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query];
+
+ [xmppStream sendElement:iq];
+
+ XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:SetActiveList name:privacyListName];
+ [self addQueryInfo:qi withKey:uuid];
+}
+
+- (NSString *)defaultListName
+{
+ return defaultListName;
+}
+
+- (NSArray *)defaultList
+{
+ return [self listWithName:defaultListName];
+}
+
+- (void)setDefaultListName:(NSString *)privacyListName
+{
+ // Setting default list:
+ //
+ // <iq type='set' id='default1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <default name='special'/>
+ // </query>
+ // </iq>
+ //
+ // Decline the use of default list:
+ //
+ // <iq type='set' id='default2'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <default/>
+ // </query>
+ // </iq>
+
+ NSXMLElement *dfault = [NSXMLElement elementWithName:@"default"];
+ if (privacyListName)
+ {
+ [dfault addAttributeWithName:@"name" stringValue:privacyListName];
+ }
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
+ [query addChild:dfault];
+
+ NSString *uuid = [xmppStream generateUUID];
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query];
+
+ [xmppStream sendElement:iq];
+
+ XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:SetDefaultList name:privacyListName];
+ [self addQueryInfo:qi withKey:uuid];
+}
+
+- (void)setListWithName:(NSString *)privacyListName items:(NSArray *)items
+{
+ // Edit a privacy list:
+ //
+ // <iq type='set' id='edit1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <list name='public'>
+ // <item type='jid' value='tybalt@example.com' action='deny' order='3'/>
+ // <item type='jid' value='paris@example.org' action='deny' order='5'/>
+ // <item action='allow' order='68'/>
+ // </list>
+ // </query>
+ // </iq>
+ //
+ //
+ // Remove a privacy list:
+ //
+ // <iq type='set' id='remove1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <list name='private'/>
+ // </query>
+ // </iq>
+
+ NSXMLElement *list = [NSXMLElement elementWithName:@"list"];
+ [list addAttributeWithName:@"name" stringValue:privacyListName];
+
+ if (items && ([items count] > 0))
+ {
+ [list setChildren:items];
+ }
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:privacy"];
+ [query addChild:list];
+
+ NSString *uuid = [xmppStream generateUUID];
+ XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:uuid child:query];
+
+ [xmppStream sendElement:iq];
+
+ XMPPPrivacyQueryInfo *qi = [XMPPPrivacyQueryInfo queryInfoWithType:EditList name:privacyListName items:items];
+ [self addQueryInfo:qi withKey:uuid];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Query Processing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)addQueryInfo:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid
+{
+ // Setup timer
+ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:QUERY_TIMEOUT
+ target:self
+ selector:@selector(queryTimeout:)
+ userInfo:uuid
+ repeats:NO];
+ queryInfo.timer = timer;
+
+ // Add to dictionary
+ [pendingQueries setObject:queryInfo forKey:uuid];
+}
+
+- (void)removeQueryInfo:(XMPPPrivacyQueryInfo *)queryInfo withKey:(NSString *)uuid
+{
+ // Invalidate timer
+ [queryInfo.timer invalidate];
+
+ // Remove from dictionary
+ [pendingQueries removeObjectForKey:uuid];
+}
+
+- (void)processQuery:(XMPPPrivacyQueryInfo *)queryInfo withFailureCode:(XMPPPrivacyErrorCode)errorCode
+{
+ NSError *error = [NSError errorWithDomain:XMPPPrivacyErrorDomain code:errorCode userInfo:nil];
+
+ if (queryInfo.type == FetchList)
+ {
+ [multicastDelegate xmppPrivacy:self didNotReceiveListNamesDueToError:error];
+ }
+ else if (queryInfo.type == FetchRules)
+ {
+ [multicastDelegate xmppPrivacy:self didNotReceiveListWithName:queryInfo.privacyListName error:error];
+ }
+ else if (queryInfo.type == EditList)
+ {
+ [multicastDelegate xmppPrivacy:self didNotSetListWithName:queryInfo.privacyListName error:error];
+ }
+ else if (queryInfo.type == SetActiveList)
+ {
+ [multicastDelegate xmppPrivacy:self didNotSetActiveListName:queryInfo.privacyListName error:error];
+ }
+ else if (queryInfo.type == SetDefaultList)
+ {
+ [multicastDelegate xmppPrivacy:self didNotSetDefaultListName:queryInfo.privacyListName error:error];
+ }
+}
+
+- (void)queryTimeout:(NSTimer *)timer
+{
+ NSString *uuid = [timer userInfo];
+
+ XMPPPrivacyQueryInfo *queryInfo = [privacyDict objectForKey:uuid];
+ if (queryInfo)
+ {
+ [self processQuery:queryInfo withFailureCode:XMPPPrivacyQueryTimeout];
+ [self removeQueryInfo:queryInfo withKey:uuid];
+ }
+}
+
+NSInteger sortItems(id itemOne, id itemTwo, void *context)
+{
+ NSXMLElement *item1 = (NSXMLElement *)itemOne;
+ NSXMLElement *item2 = (NSXMLElement *)itemTwo;
+
+ NSString *orderStr1 = [item1 attributeStringValueForName:@"order"];
+ NSString *orderStr2 = [item2 attributeStringValueForName:@"order"];
+
+ NSUInteger order1;
+ BOOL parse1 = [NSNumber parseString:orderStr1 intoNSUInteger:&order1];
+
+ NSUInteger order2;
+ BOOL parse2 = [NSNumber parseString:orderStr2 intoNSUInteger:&order2];
+
+ if (parse1)
+ {
+ if (parse2)
+ {
+ // item1 = valid
+ // item2 = valid
+
+ if (order1 < order2)
+ return NSOrderedAscending;
+ if (order1 > order2)
+ return NSOrderedDescending;
+
+ return NSOrderedSame;
+ }
+ else
+ {
+ // item1 = valid
+ // item2 = invalid
+
+ return NSOrderedAscending;
+ }
+ }
+ else if (parse2)
+ {
+ // item1 = invalid
+ // item2 = valid
+
+ return NSOrderedDescending;
+ }
+ else
+ {
+ // item1 = invalid
+ // item2 = invalid
+
+ return NSOrderedSame;
+ }
+}
+
+- (void)processQueryResponse:(XMPPIQ *)iq withInfo:(XMPPPrivacyQueryInfo *)queryInfo
+{
+ if (queryInfo.type == FetchList)
+ {
+ // Privacy List Names Query Response:
+ //
+ // <iq type='result' id='getlist1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <active name='private'/>
+ // <default name='public'/>
+ // <list name='public'/>
+ // <list name='private'/>
+ // <list name='special'/>
+ // </query>
+ // </iq>
+
+ if ([[iq type] isEqualToString:@"result"])
+ {
+ [activeListName release];
+ [defaultListName release];
+
+ NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"];
+ if (query == nil) return;
+
+ NSXMLElement *active = [query elementForName:@"active"];
+ activeListName = [[active attributeStringValueForName:@"name"] copy];
+
+ NSXMLElement *dfault = [query elementForName:@"default"];
+ defaultListName = [[dfault attributeStringValueForName:@"name"] copy];
+
+ NSArray *listNames = [query elementsForName:@"list"];
+ for (NSXMLElement *listName in listNames)
+ {
+ NSString *name = [listName attributeStringValueForName:@"name"];
+ if (name)
+ {
+ id value = [privacyDict objectForKey:name];
+ if (value == nil)
+ {
+ [privacyDict setObject:[NSNull null] forKey:name];
+ }
+ }
+ }
+
+ [multicastDelegate xmppPrivacy:self didReceiveListNames:[self listNames]];
+ }
+ else
+ {
+ [multicastDelegate xmppPrivacy:self didNotReceiveListNamesDueToError:iq];
+ }
+ }
+ else if (queryInfo.type == FetchRules)
+ {
+ // Privacy List Rules Query Response (success):
+ //
+ // <iq type='result' id='getlist2'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <list name='public'>
+ // <item type='jid' value='tybalt@example.com' action='deny' order='1'/>
+ // <item action='allow' order='2'/>
+ // </list>
+ // </query>
+ // </iq>
+ //
+ //
+ // Privacy List Rules Query Response (error):
+ //
+ // <iq type='error' id='getlist5'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <list name='The Empty Set'/>
+ // </query>
+ // <error type='cancel'>
+ // <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ // </error>
+ // </iq>
+
+ if ([[iq type] isEqualToString:@"result"])
+ {
+ NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"];
+ if (query == nil) return;
+
+ NSXMLElement *list = [query elementForName:@"list"];
+ if (list == nil) return;
+
+ NSArray *items = [[list elementsForName:@"item"] sortedArrayUsingFunction:sortItems context:NULL];
+
+ if (items == nil)
+ {
+ items = [NSArray array];
+ }
+
+ [privacyDict setObject:items forKey:queryInfo.privacyListName];
+
+ [multicastDelegate xmppPrivacy:self didReceiveListWithName:queryInfo.privacyListName items:items];
+ }
+ else
+ {
+ [multicastDelegate xmppPrivacy:self didNotReceiveListWithName:queryInfo.privacyListName error:iq];
+ }
+ }
+ else if (queryInfo.type == EditList)
+ {
+ // Privacy List Add/Edit/Remove Response (success):
+ //
+ // <iq type='result' id='abc123' to='romeo@example.net/orchard'/>
+ //
+ // Note: The result iq does NOT have a query child.
+
+ if ([[iq type] isEqualToString:@"result"])
+ {
+ NSArray *items = [[queryInfo privacyListItems] sortedArrayUsingFunction:sortItems context:NULL];
+
+ if (items == nil)
+ {
+ items = [NSArray array];
+ }
+
+ [privacyDict setObject:items forKey:queryInfo.privacyListName];
+
+ [multicastDelegate xmppPrivacy:self didSetListWithName:queryInfo.privacyListName];
+ }
+ else
+ {
+ [multicastDelegate xmppPrivacy:self didNotSetListWithName:queryInfo.privacyListName error:iq];
+ }
+ }
+ else if (queryInfo.type == SetActiveList)
+ {
+ // Change of active list (success):
+ //
+ // <iq type='result' id='active1' to='romeo@example.net/orchard'/>
+ //
+ //
+ // Change of active list (error):
+ //
+ // <iq to='romeo@example.net/orchard' type='error' id='active2'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <active name='Invalid List Name'/>
+ // </query>
+ // <error type='cancel'>
+ // <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ // </error>
+ // </iq>
+ //
+ //
+ // Note: The result iq does NOT have a query child.
+ // Note: The success could also mean we declined the use of an active list.
+
+ if ([[iq type] isEqualToString:@"result"])
+ {
+ [activeListName release];
+ activeListName = [[queryInfo privacyListName] copy];
+
+ [multicastDelegate xmppPrivacy:self didSetActiveListName:queryInfo.privacyListName];
+ }
+ else
+ {
+ [multicastDelegate xmppPrivacy:self didNotSetActiveListName:queryInfo.privacyListName error:iq];
+ }
+ }
+ else if (queryInfo.type == SetDefaultList)
+ {
+ // Change of default list (success):
+ //
+ // <iq type='result' id='default1' to='romeo@example.net/orchard'/>
+ //
+ //
+ // Change of default list (error):
+ //
+ // <iq to='romeo@example.net/orchard' type='error' id='default1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <default name='special'/>
+ // </query>
+ // <error type='cancel'>
+ // <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ // </error>
+ // </iq>
+ //
+ //
+ // Note: The result iq does NOT have a query child.
+ // Note: The success could also mean we declined the use of a default list.
+
+ if ([[iq type] isEqualToString:@"result"])
+ {
+ [defaultListName release];
+ defaultListName = [[queryInfo privacyListName] copy];
+
+ [multicastDelegate xmppPrivacy:self didSetDefaultListName:queryInfo.privacyListName];
+ }
+ else
+ {
+ [multicastDelegate xmppPrivacy:self didNotSetDefaultListName:queryInfo.privacyListName error:iq];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark XMPPStream Delegate
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
+{
+ if (self.autoRetrievePrivacyListNames)
+ {
+ [self retrieveListNames];
+ }
+}
+
+- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
+{
+ NSString *type = [iq type];
+
+ if ([type isEqualToString:@"set"])
+ {
+ NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:privacy"];
+ if (query)
+ {
+ // Privacy List Push:
+ //
+ // <iq type='set' id='push1'>
+ // <query xmlns='jabber:iq:privacy'>
+ // <list name='public'/>
+ // </query>
+ // </iq>
+ //
+ //
+ // Push response:
+ //
+ // <iq type='result' id='push1'/>
+
+ NSXMLElement *list = [query elementForName:@"list"];
+
+ NSString *listName = [list attributeStringValueForName:@"name"];
+ if (listName == nil)
+ {
+ return NO;
+ }
+
+ [multicastDelegate xmppPrivacy:self didReceivePushWithListName:listName];
+
+ XMPPIQ *iqResponse = [XMPPIQ iqWithType:@"result" to:[iq from] elementID:[iq elementID]];
+ [xmppStream sendElement:iqResponse];
+
+ if (self.autoRetrievePrivacyListItems)
+ {
+ [self retrieveListWithName:listName];
+ }
+
+ return YES;
+ }
+ }
+ else
+ {
+ // This may be a response to a query we sent
+
+ XMPPPrivacyQueryInfo *queryInfo = [pendingQueries objectForKey:[iq elementID]];
+ if (queryInfo)
+ {
+ [self processQueryResponse:iq withInfo:queryInfo];
+
+ if (queryInfo.type == FetchList && self.autoRetrievePrivacyListItems)
+ {
+ for (NSString *privacyListName in privacyDict)
+ {
+ id privacyListItems = [privacyDict objectForKey:privacyListName];
+
+ if (privacyListItems == [NSNull null])
+ {
+ [self retrieveListWithName:privacyListName];
+ }
+ }
+ }
+
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+- (void)xmppStreamDidDisconnect:(XMPPStream *)sender
+{
+ // If there are any pending queries,
+ // they just failed due to the disconnection.
+
+ for (NSString *uuid in pendingQueries)
+ {
+ XMPPPrivacyQueryInfo *queryInfo = [privacyDict objectForKey:uuid];
+
+ [self processQuery:queryInfo withFailureCode:XMPPPrivacyDisconnect];
+ }
+
+ // Clear the list of pending queries
+
+ [pendingQueries removeAllObjects];
+
+ // Maybe clear all stored privacy info
+
+ if (self.autoClearPrivacyListInfo)
+ {
+ [self clearPrivacyListInfo];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Privacy Items
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSXMLElement *)privacyItemWithAction:(NSString *)action order:(NSUInteger)orderValue
+{
+ return [self privacyItemWithType:nil value:nil action:action order:orderValue];
+}
+
++ (NSXMLElement *)privacyItemWithType:(NSString *)type
+ value:(NSString *)value
+ action:(NSString *)action
+ order:(NSUInteger)orderValue
+{
+ NSString *order = [[NSString alloc] initWithFormat:@"%lu", (unsigned long)orderValue];
+
+ // <item type='[jid|group|subscription]'
+ // value='bar'
+ // action='[allow|deny]'
+ // order='unsignedInt'>
+ // [<iq/>]
+ // [<message/>]
+ // [<presence-in/>]
+ // [<presence-out/>]
+ // </item>
+
+ NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
+
+ if (type)
+ [item addAttributeWithName:@"type" stringValue:type];
+
+ if (value)
+ [item addAttributeWithName:@"value" stringValue:value];
+
+ [item addAttributeWithName:@"action" stringValue:action];
+ [item addAttributeWithName:@"order" stringValue:order];
+
+ [order release];
+ return item;
+}
+
++ (void)blockIQs:(NSXMLElement *)privacyItem
+{
+ [privacyItem addChild:[NSXMLElement elementWithName:@"iq"]];
+}
+
++ (void)blockMessages:(NSXMLElement *)privacyItem
+{
+ [privacyItem addChild:[NSXMLElement elementWithName:@"message"]];
+}
+
++ (void)blockPresenceIn:(NSXMLElement *)privacyItem
+{
+ [privacyItem addChild:[NSXMLElement elementWithName:@"presence-in"]];
+}
+
++ (void)blockPresenceOut:(NSXMLElement *)privacyItem
+{
+ [privacyItem addChild:[NSXMLElement elementWithName:@"presence-out"]];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPPrivacyQueryInfo
+
+@synthesize type;
+@synthesize privacyListName;
+@synthesize privacyListItems;
+@synthesize timer;
+
+
+- (id)initWithType:(XMPPPrivacyQueryInfoType)aType name:(NSString *)name items:(NSArray *)items
+{
+ if ((self = [super init]))
+ {
+ type = aType;
+ privacyListName = [name copy];
+ privacyListItems = [items copy];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [privacyListName release];
+ [privacyListItems release];
+ [timer invalidate];
+ [timer release];
+ [super dealloc];
+}
+
++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type
+{
+ return [self queryInfoWithType:type name:nil items:nil];
+}
+
++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name
+{
+ return [self queryInfoWithType:type name:name items:nil];
+}
+
++ (XMPPPrivacyQueryInfo *)queryInfoWithType:(XMPPPrivacyQueryInfoType)type name:(NSString *)name items:(NSArray *)items
+{
+ return [[[XMPPPrivacyQueryInfo alloc] initWithType:type name:name items:items] autorelease];
+}
+
+@end
View
16 Xcode/DesktopXMPP/AppDelegate.h
@@ -6,14 +6,14 @@
@interface AppDelegate : NSObject
{
- __strong XMPPStream *xmppStream;
- __strong XMPPReconnect *xmppReconnect;
- __strong XMPPRoster *xmppRoster;
- __strong XMPPRosterMemoryStorage *xmppRosterStorage;
- __strong XMPPCapabilities *xmppCapabilities;
- __strong XMPPCapabilitiesCoreDataStorage *xmppCapabilitiesStorage;
- __strong XMPPPing *xmppPing;
- __strong XMPPTime *xmppTime;
+ XMPPStream *xmppStream;
+ XMPPReconnect *xmppReconnect;
+ XMPPRoster *xmppRoster;
+ XMPPRosterMemoryStorage *xmppRosterStorage;
+ XMPPCapabilities *xmppCapabilities;
+ XMPPCapabilitiesCoreDataStorage *xmppCapabilitiesStorage;
+ XMPPPing *xmppPing;
+ XMPPTime *xmppTime;
NSMutableArray *turnSockets;
View
48 Xcode/DesktopXMPP/AppDelegate.m
@@ -21,11 +21,19 @@ - (id)init
{
if ((self = [super init]))
{
+ //
// Configure logging framework
+ //
+ // The XMPPFramework uses the CocoaLumberjack framework to provide fast & flexible logging.
+ // There's tons of information about Lumberjack online:
+ // https://github.com/robbiehanson/CocoaLumberjack
+ // https://github.com/robbiehanson/CocoaLumberjack/wiki
+ //
+ // But this one line is all we need to configure the logging framework to dump to the Xcode console.
[DDLog addLogger:[DDTTYLogger sharedInstance]];
- // Initialize variables
+ // Initialize xmpp stream and modules
xmppStream = [[XMPPStream alloc] init];
@@ -43,6 +51,21 @@ - (id)init
// xmppPing = [[XMPPPing alloc] init];
// xmppTime = [[XMPPTime alloc] init];
+ // Activate xmpp modules
+
+ [xmppReconnect activate:xmppStream];
+ [xmppRoster activate:xmppStream];
+ [xmppCapabilities activate:xmppStream];
+ [xmppPing activate:xmppStream];
+ [xmppTime activate:xmppStream];
+
+ // Add ourself as a delegate to anything we may be interested in
+
+ [xmppReconnect addDelegate:self delegateQueue:dispatch_get_main_queue()];
+ [xmppCapabilities addDelegate:self delegateQueue:dispatch_get_main_queue()];
+
+ // Initialize other stuff
+
turnSockets = [[NSMutableArray alloc] init];
}
return self;
@@ -52,34 +75,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
DDLogInfo(@"%@: %@", THIS_FILE, THIS_METHOD);
- [xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
-
- // Activate xmpp modules
-
- [xmppReconnect activate:xmppStream];
- [xmppRoster activate:xmppStream];
- [xmppCapabilities activate:xmppStream];
- [xmppPing activate:xmppStream];
- [xmppTime activate:xmppStream];
-
- // Add ourself as a delegate to anything we may be interested in
-
-// [xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
- [xmppReconnect addDelegate:self delegateQueue:dispatch_get_main_queue()];
- [xmppCapabilities addDelegate:self delegateQueue:dispatch_get_main_queue()];
- [xmppPing addDelegate:self delegateQueue:dispatch_get_main_queue()];
- [xmppTime addDelegate:self delegateQueue:dispatch_get_main_queue()];
-
// Start the GUI stuff
[rosterController displaySignInSheet];
}
-- (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module
-{
- DDLogVerbose(@"%@: xmppStream:didRegisterModule: %@", THIS_FILE, module);
-}
-
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XEP-0065 Support
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
View
9 Xcode/DesktopXMPP/RosterController.m
@@ -184,10 +184,10 @@ - (void)unlockSignInUI
- (void)updateAccountInfo
{
NSString *domain = [serverField stringValue];
- [[self xmppStream] setHostName:domain];
+ self.xmppStream.hostName = domain;
int port = [portField intValue];
- [[self xmppStream] setHostPort:port];
+ self.xmppStream.hostPort = port;
useSSL = ([sslButton state] == NSOnState);
allowSelfSignedCertificates = ([selfSignedButton state] == NSOnState);
@@ -200,9 +200,8 @@ - (void)updateAccountInfo
}
XMPPJID *jid = [XMPPJID jidWithString:[jidField stringValue] resource:resource];
+ self.xmppStream.myJID = jid;
- [[self xmppStream] setMyJID:jid];
-
// Update persistent info
NSUserDefaults *dflts = [NSUserDefaults standardUserDefaults];
@@ -273,6 +272,8 @@ - (IBAction)createAccount:(id)sender
- (IBAction)signIn:(id)sender
{
+ [signInSheet makeFirstResponder:nil]; // workaround for some odd UI bug I don't understand
+
[self updateAccountInfo];
NSError *error = nil;

0 comments on commit b7ad73f

Please sign in to comment.