- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,14 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPStreamManagement.h"
|
||||
|
||||
/**
|
||||
* This class provides an in-memory only storage system for XMPPStreamManagement.
|
||||
* As such, it will only support stream resumption so long as the application doesn't terminate.
|
||||
*
|
||||
* This class should be considered primarily for testing.
|
||||
* An application making use of stream management should likely transition
|
||||
* to a persistent storage layer before distribution.
|
||||
**/
|
||||
@interface XMPPStreamManagementMemoryStorage : NSObject <XMPPStreamManagementStorage>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,211 @@
|
||||
#import "XMPPStreamManagementMemoryStorage.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
|
||||
@interface XMPPStreamManagementMemoryStorage ()
|
||||
|
||||
@property (atomic, weak, readwrite) XMPPStreamManagement *parent;
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation XMPPStreamManagementMemoryStorage
|
||||
{
|
||||
int32_t isConfigured;
|
||||
|
||||
NSString *resumptionId;
|
||||
uint32_t timeout;
|
||||
|
||||
NSDate *lastDisconnect;
|
||||
uint32_t lastHandledByClient;
|
||||
uint32_t lastHandledByServer;
|
||||
NSArray *pendingOutgoingStanzas;
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)configureWithParent:(XMPPStreamManagement *)parent queue:(dispatch_queue_t)queue
|
||||
{
|
||||
// This implementation only supports a single xmppStream.
|
||||
// You must create multiple instances for multiple xmppStreams.
|
||||
|
||||
return OSAtomicCompareAndSwap32(0, 1, &isConfigured);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after we receive <enabled/> from the server.
|
||||
*
|
||||
* @param resumptionId
|
||||
* The ID required to resume the session, given to us by the server.
|
||||
*
|
||||
* @param timeout
|
||||
* The timeout in seconds.
|
||||
* After a disconnect, the server will maintain our state for this long.
|
||||
* If we attempt to resume the session after this timeout it likely won't work.
|
||||
*
|
||||
* @param lastDisconnect
|
||||
* Used to reset the lastDisconnect value.
|
||||
* This value is often updated during the session, to ensure it closely resemble the date the server will use.
|
||||
* That is, if the client application is killed (or crashes) we want a relatively accurate lastDisconnect date.
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
*
|
||||
* This method should also nil out the following values (if needed) associated with the account:
|
||||
* - lastHandledByClient
|
||||
* - lastHandledByServer
|
||||
* - pendingOutgoingStanzas
|
||||
**/
|
||||
- (void)setResumptionId:(NSString *)inResumptionId
|
||||
timeout:(uint32_t)inTimeout
|
||||
lastDisconnect:(NSDate *)inLastDisconnect
|
||||
forStream:(XMPPStream *)stream
|
||||
{
|
||||
resumptionId = inResumptionId;
|
||||
timeout = inTimeout;
|
||||
lastDisconnect = inLastDisconnect;
|
||||
|
||||
lastHandledByClient = 0;
|
||||
lastHandledByServer = 0;
|
||||
pendingOutgoingStanzas = nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked ** often ** during stream operation.
|
||||
* It is not invoked when the xmppStream is disconnected.
|
||||
*
|
||||
* Important: See the note [in XMPPStreamManagement.h]: "Optimizing storage demands during active stream usage"
|
||||
*
|
||||
* @param date
|
||||
* Updates the previous lastDisconnect value.
|
||||
*
|
||||
* @param lastHandledByClient
|
||||
* The most recent 'h' value we can safely send to the server.
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
**/
|
||||
- (void)setLastDisconnect:(NSDate *)inLastDisconnect
|
||||
lastHandledByClient:(uint32_t)inLastHandledByClient
|
||||
forStream:(XMPPStream *)stream
|
||||
{
|
||||
lastDisconnect = inLastDisconnect;
|
||||
lastHandledByClient = inLastHandledByClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked ** often ** during stream operation.
|
||||
* It is not invoked when the xmppStream is disconnected.
|
||||
*
|
||||
* Important: See the note [in XMPPStreamManagement.h]: "Optimizing storage demands during active stream usage"
|
||||
*
|
||||
* @param date
|
||||
* Updates the previous lastDisconnect value.
|
||||
*
|
||||
* @param lastHandledByServer
|
||||
* The most recent 'h' value we've received from the server.
|
||||
*
|
||||
* @param pendingOutgoingStanzas
|
||||
* An array of XMPPStreamManagementOutgoingStanza objects.
|
||||
* The storage layer is in charge of properly persisting this array, including:
|
||||
* - the array count
|
||||
* - the stanzaId of each element, including those that are nil
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
**/
|
||||
- (void)setLastDisconnect:(NSDate *)inLastDisconnect
|
||||
lastHandledByServer:(uint32_t)inLastHandledByServer
|
||||
pendingOutgoingStanzas:(NSArray *)inPendingOutgoingStanzas
|
||||
forStream:(XMPPStream *)stream
|
||||
{
|
||||
lastDisconnect = inLastDisconnect;
|
||||
lastHandledByServer = inLastHandledByServer;
|
||||
pendingOutgoingStanzas = inPendingOutgoingStanzas;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked immediately after an accidental disconnect.
|
||||
* And may be invoked post-disconnect if the state changes, such as for the following edge cases:
|
||||
*
|
||||
* - due to continued processing of stanzas received pre-disconnect,
|
||||
* that are just now being marked as handled by the delegate(s)
|
||||
* - due to a delayed response from the delegate(s),
|
||||
* such that we didn't receive the stanzaId for an outgoing stanza until after the disconnect occurred.
|
||||
*
|
||||
* This method is not invoked if stream management is started on a connected xmppStream.
|
||||
*
|
||||
* @param date
|
||||
* This value will be the actual disconnect date.
|
||||
*
|
||||
* @param lastHandledByClient
|
||||
* The most recent 'h' value we can safely send to the server.
|
||||
*
|
||||
* @param lastHandledByServer
|
||||
* The most recent 'h' value we've received from the server.
|
||||
*
|
||||
* @param pendingOutgoingStanzas
|
||||
* An array of XMPPStreamManagementOutgoingStanza objects.
|
||||
* The storage layer is in charge of properly persisting this array, including:
|
||||
* - the array count
|
||||
* - the stanzaId of each element, including those that are nil
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
**/
|
||||
- (void)setLastDisconnect:(NSDate *)inLastDisconnect
|
||||
lastHandledByClient:(uint32_t)inLastHandledByClient
|
||||
lastHandledByServer:(uint32_t)inLastHandledByServer
|
||||
pendingOutgoingStanzas:(NSArray *)inPendingOutgoingStanzas
|
||||
forStream:(XMPPStream *)stream
|
||||
{
|
||||
lastDisconnect = inLastDisconnect;
|
||||
lastHandledByClient = inLastHandledByClient;
|
||||
lastHandledByServer = inLastHandledByServer;
|
||||
pendingOutgoingStanzas = inPendingOutgoingStanzas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the extension needs values from a previous session.
|
||||
* This method is used to get values needed in order to determine if it can resume a previous stream.
|
||||
**/
|
||||
- (void)getResumptionId:(NSString **)resumptionIdPtr
|
||||
timeout:(uint32_t *)timeoutPtr
|
||||
lastDisconnect:(NSDate **)lastDisconnectPtr
|
||||
forStream:(XMPPStream *)stream
|
||||
{
|
||||
if (resumptionIdPtr) *resumptionIdPtr = resumptionId;
|
||||
if (timeoutPtr) *timeoutPtr = timeout;
|
||||
if (lastDisconnectPtr) *lastDisconnectPtr = lastDisconnect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the extension needs values from a previous session.
|
||||
* This method is used to get values needed in order to resume a previous stream.
|
||||
**/
|
||||
- (void)getLastHandledByClient:(uint32_t *)lastHandledByClientPtr
|
||||
lastHandledByServer:(uint32_t *)lastHandledByServerPtr
|
||||
pendingOutgoingStanzas:(NSArray **)pendingOutgoingStanzasPtr
|
||||
forStream:(XMPPStream *)stream;
|
||||
{
|
||||
if (lastHandledByClientPtr) *lastHandledByClientPtr = lastHandledByClient;
|
||||
if (lastHandledByServerPtr) *lastHandledByServerPtr = lastHandledByServer;
|
||||
if (pendingOutgoingStanzasPtr) *pendingOutgoingStanzasPtr = pendingOutgoingStanzas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the storage layer to remove all values stored for the given stream.
|
||||
* This occurs after the extension detects a "cleanly closed stream",
|
||||
* in which case the stream cannot be resumed next time.
|
||||
**/
|
||||
- (void)removeAllForStream:(XMPPStream *)stream
|
||||
{
|
||||
resumptionId = nil;
|
||||
timeout = 0;
|
||||
|
||||
lastDisconnect = nil;
|
||||
lastHandledByClient = 0;
|
||||
lastHandledByServer = 0;
|
||||
pendingOutgoingStanzas = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,35 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPElement.h"
|
||||
|
||||
/**
|
||||
* An outgoing stanza.
|
||||
*
|
||||
* The translation from element to stanzaId may be an asynchronous process,
|
||||
* so this structure is used to assist in the process.
|
||||
**/
|
||||
@interface XMPPStreamManagementOutgoingStanza : NSObject <NSCopying, NSCoding>
|
||||
|
||||
- (instancetype)initAwaitingStanzaId;
|
||||
- (instancetype)initWithStanzaId:(id)stanzaId;
|
||||
|
||||
@property (nonatomic, strong, readwrite) id stanzaId;
|
||||
@property (nonatomic, assign, readwrite) BOOL awaitingStanzaId;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* An incoming stanza.
|
||||
*
|
||||
* The translation from element to stanzaId may be an asynchronous process,
|
||||
* so this structure is used to assist in the process.
|
||||
**/
|
||||
@interface XMPPStreamManagementIncomingStanza : NSObject
|
||||
|
||||
- (instancetype)initWithStanzaId:(id)stanzaId isHandled:(BOOL)isHandled;
|
||||
|
||||
@property (nonatomic, strong, readwrite) id stanzaId;
|
||||
@property (nonatomic, assign, readwrite) BOOL isHandled;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,85 @@
|
||||
#import "XMPPStreamManagementStanzas.h"
|
||||
|
||||
|
||||
@implementation XMPPStreamManagementOutgoingStanza
|
||||
|
||||
@synthesize awaitingStanzaId = awaitingStanzaId;
|
||||
@synthesize stanzaId = stanzaId;
|
||||
|
||||
/**
|
||||
* Use when the stanzaId is unknown, and we are awaiting a stanzaId from the delegate(s).
|
||||
**/
|
||||
- (instancetype)initAwaitingStanzaId
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
awaitingStanzaId = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when the stanzaId is known, meaning we are NOT awaiting a stanzaId from the delegate(s).
|
||||
* The stanzaId may be nil.
|
||||
**/
|
||||
- (instancetype)initWithStanzaId:(id)inStanzaId
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
stanzaId = inStanzaId;
|
||||
awaitingStanzaId = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/* NSCopying */
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
XMPPStreamManagementOutgoingStanza *copy = [[XMPPStreamManagementOutgoingStanza alloc] init];
|
||||
copy->awaitingStanzaId = awaitingStanzaId;
|
||||
copy->stanzaId = stanzaId;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/* NSCoding */
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)decoder
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
awaitingStanzaId = [decoder decodeBoolForKey:@"awaitingStanzaId"];
|
||||
stanzaId = [decoder decodeObjectForKey:@"stanzaId"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[coder encodeBool:awaitingStanzaId forKey:@"awaitingStanzaId"];
|
||||
[coder encodeObject:stanzaId forKey:@"stanzaId"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPStreamManagementIncomingStanza
|
||||
|
||||
@synthesize stanzaId = stanzaId;
|
||||
@synthesize isHandled = isHandled;
|
||||
|
||||
- (instancetype)initWithStanzaId:(id)inStanzaId isHandled:(BOOL)inIsHandled
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
stanzaId = inStanzaId;
|
||||
isHandled = inIsHandled;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,617 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
#define _XMPP_STREAM_MANAGEMENT_H
|
||||
|
||||
@protocol XMPPStreamManagementStorage;
|
||||
|
||||
|
||||
@interface XMPPStreamManagement : XMPPModule <XMPPCustomBinding>
|
||||
|
||||
/**
|
||||
* The XMPPStreamManagement extension implements XEP-0198:
|
||||
* http://xmpp.org/extensions/xep-0198.html
|
||||
*
|
||||
* @param storage
|
||||
* You must configure the extension with a storage module.
|
||||
* A persistent storage layer is recommended for distribution.
|
||||
* For testing, or if you're not planning on using stream resumption, then the memory storage solution will work.
|
||||
*
|
||||
* @param queue
|
||||
* The standard dispatch_queue option, with which to run the extension on.
|
||||
**/
|
||||
- (id)initWithStorage:(id <XMPPStreamManagementStorage>)storage;
|
||||
- (id)initWithStorage:(id <XMPPStreamManagementStorage>)storage dispatchQueue:(dispatch_queue_t)queue;
|
||||
|
||||
@property (nonatomic, strong, readonly) id <XMPPStreamManagementStorage> storage;
|
||||
|
||||
#pragma mark Enable
|
||||
|
||||
/**
|
||||
* This method sends the <enable> stanza to the server to request enabling stream management.
|
||||
*
|
||||
* XEP-0198 specifies that the <enable> stanza should only be sent by clients after authentication,
|
||||
* and after binding has occurred.
|
||||
*
|
||||
* The servers response is reported via the delegate methods:
|
||||
* @see xmppStreamManagement:wasEnabled:
|
||||
* @see xmppStreamManagement:wasNotEnabled:
|
||||
*
|
||||
* @param supportsResumption
|
||||
* Whether the client should request resumptions support.
|
||||
* If YES, the resume attribute will be included. E.g. <enable resume='true'/>
|
||||
*
|
||||
* @param maxTimeout
|
||||
* Allows you to specify the client's preferred maximum resumption time.
|
||||
* This is optional, and will only be sent if you provide a positive value (maxTimeout > 0.0).
|
||||
* Note that XEP-0198 only supports sending this value in seconds.
|
||||
* So it the provided maxTimeout includes millisecond precision, this will be ignored via truncation
|
||||
* (rounding down to nearest whole seconds value).
|
||||
*
|
||||
* @see supportsStreamManagement
|
||||
**/
|
||||
- (void)enableStreamManagementWithResumption:(BOOL)supportsResumption maxTimeout:(NSTimeInterval)maxTimeout;
|
||||
|
||||
#pragma mark Resume
|
||||
|
||||
/**
|
||||
* If set to YES, then the extension will automatically attempt to resume any sessions that appear resumable.
|
||||
*
|
||||
* That is, if the canResumeStream method would return YES, then the module will automatically plug into the xmppStream,
|
||||
* and attempts to resume the session. If the attempt fails, the xmppStream will automatically fall back to
|
||||
* the standard binding process.
|
||||
*
|
||||
* Remember: If the extension does not believe that resumption is possible, then it won't attempt to resume.
|
||||
* That is, if it doesn't have data in storage that matches the current connection, or the data is expired,
|
||||
* then it allows the xmppStream to perform standard binding immediately, without attempting to resume.
|
||||
*
|
||||
* If you wish to handle stream resumption manually, then you can simply implement xmppStreamWillBind:,
|
||||
* and return this extension instance according to your own conditions.
|
||||
*
|
||||
* In order to determine if a stream was resumed, you should invoke didResumeWithAckedStanzaIds:serverResponse:
|
||||
* from within the xmppStreamDidAuthenticate: callback.
|
||||
*
|
||||
* The default value is NO.
|
||||
**/
|
||||
@property (atomic, readwrite) BOOL autoResume;
|
||||
|
||||
/**
|
||||
* This method is meant to be called by other extensions when they receive an xmppStreamDidAuthenticate callback.
|
||||
*
|
||||
* Returns YES if the stream was resumed during the authentication process.
|
||||
* Returns NO otherwise (if resume wasn't available, or it failed).
|
||||
*
|
||||
* Other extensions may wish to skip certain setup processes that aren't
|
||||
* needed if the stream was resumed (since the previous session state has been restored server-side).
|
||||
**/
|
||||
@property (atomic, readonly) BOOL didResume;
|
||||
|
||||
/**
|
||||
* This method is meant to be called when you receive an xmppStreamDidAuthenticate callback.
|
||||
*
|
||||
* It is used instead of a standard delegate method in order to provide a cleaner API.
|
||||
* By using this method, one can put all the logic for handling authentication in a single place.
|
||||
* But more importantly, it solves several subtle timing and threading issues.
|
||||
*
|
||||
* > A delegate method could have hit either before or after xmppStreamDidAuthenticate, depending on thread scheduling.
|
||||
* > We could have queued it up, and forced it to hit after.
|
||||
* > But your code would likely still have needed to add a check within xmppStreamDidAuthenticate...
|
||||
*
|
||||
* @param stanzaIdsPtr (optional)
|
||||
* Just like the stanzaIdsPtr provided in xmppStreamManagement:didReceiveAckForStanzaIds:.
|
||||
* This comes from the h value provided within the <resumed h='X'/> stanza sent by the server.
|
||||
*
|
||||
* @param responsePtr (optional)
|
||||
* Returns the response we got from the server. Either <resumed/> or <failed/>.
|
||||
* This will be nil if resume wasn't tried.
|
||||
*
|
||||
* @return
|
||||
* YES if the stream was resumed.
|
||||
* NO otherwise.
|
||||
**/
|
||||
- (BOOL)didResumeWithAckedStanzaIds:(NSArray **)stanzaIdsPtr
|
||||
serverResponse:(NSXMLElement **)responsePtr;
|
||||
|
||||
/**
|
||||
* Returns YES if the stream can be resumed.
|
||||
*
|
||||
* This would be the case if there's an available resumptionId for the authenticated xmppStream,
|
||||
* and the timeout from the last stream has not been exceeded.
|
||||
**/
|
||||
- (BOOL)canResumeStream;
|
||||
|
||||
|
||||
#pragma mark Requesting Acks
|
||||
|
||||
/**
|
||||
* Sends a request <r/> element, requesting the server reply with an ack <a h='lastHandled'/>.
|
||||
*
|
||||
* You can also configure the extension to automatically sends requests.
|
||||
* @see automaticallyRequestAcksAfterStanzaCount:orTimeout:
|
||||
*
|
||||
* When the server replies with an ack, the delegate method will be invoked.
|
||||
* @see xmppStreamManagement:didReceiveAckForStanzaIds:
|
||||
**/
|
||||
- (void)requestAck;
|
||||
|
||||
/**
|
||||
* The module can be configured to automatically request acks (send <r/>) based on your criteria.
|
||||
* The algorithm to do this takes into account:
|
||||
*
|
||||
* - The number of stanzas that have been sent since the last request was sent.
|
||||
* - The amount of time that has elapsed since the first stanza (after the last request) was sent.
|
||||
*
|
||||
* So, for example, if you set the stanzaCount to 5, and the timeout to 2.0 seconds then:
|
||||
* - Sending 5 stanzas back-to-back will automatically trigger an outgoing request
|
||||
* - Sending 1 stanza will automatically trigger an outgoing request to be sent 2.0 seconds later,
|
||||
* which will get preempted if 4 more stanzas are sent before the 2.0 second timer expires.
|
||||
*
|
||||
* In other words, whichever event takes place FIRST will trigger the request to be sent.
|
||||
*
|
||||
* You can disable either trigger by setting its value to zero.
|
||||
* So, for example, if you only want to use a timeout of 5 seconds,
|
||||
* then you could set the stanzaCount to zero and the timeout to 5 seconds.
|
||||
*
|
||||
* @param stanzaCount
|
||||
* The stanzaCount to use for the auto request algorithm.
|
||||
* If stanzaCount is zero, then the number of stanzas will be ignored in the algorithm.
|
||||
*
|
||||
* @param timeout
|
||||
* The timeout to use for the auto request algorithm.
|
||||
* If the timeout is zero (or negative), then the timer will be ignored in the algorithm.
|
||||
*
|
||||
* The default stanzaCount is 0 (disabled).
|
||||
* The default timeout is 0.0 seconds (disabled).
|
||||
**/
|
||||
- (void)automaticallyRequestAcksAfterStanzaCount:(NSUInteger)stanzaCount orTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
/**
|
||||
* Returns the current auto-request configuration.
|
||||
*
|
||||
* @see automaticallyRequestAcksAfterStanzaCount:orTimeout:
|
||||
**/
|
||||
- (void)getAutomaticallyRequestAcksAfterStanzaCount:(NSUInteger *)stanzaCountPtr orTimeout:(NSTimeInterval *)timeoutPtr;
|
||||
|
||||
|
||||
#pragma mark Sending Acks
|
||||
|
||||
/**
|
||||
* Sends an unrequested ack <a h='lastHandled'/> element, acking the server's recently received (and handled) elements.
|
||||
*
|
||||
* You can also configure the extension to automatically sends acks.
|
||||
* @see automaticallySendAcksAfterStanzaCount:orTimeout:
|
||||
*
|
||||
* Keep in mind that the extension will automatically send an ack if it receives an explicit request.
|
||||
**/
|
||||
- (void)sendAck;
|
||||
|
||||
/**
|
||||
* The module can be configured to automatically send unrequested acks.
|
||||
* That is, rather than waiting to receive explicit requests <r/> from the server,
|
||||
* the client automatically sends them based on configurable criteria.
|
||||
*
|
||||
* The algorithm to do this takes into account:
|
||||
*
|
||||
* - The number of stanzas that have been received since the last ack was sent.
|
||||
* - The amount of time that has elapsed since the first stanza (after the last ack) was received.
|
||||
*
|
||||
* In other words, whichever event takes place FIRST will trigger the request to be sent.
|
||||
* You can disable either trigger by setting its value to zero.
|
||||
*
|
||||
* As would be expected, if you manually send an unrequested ack (via the sendAck method),
|
||||
* or if an ack is sent out in response to a received request </r> from the server,
|
||||
* then the stanzaCount & timeout are reset.
|
||||
*
|
||||
* @param stanzaCount
|
||||
* The stanzaCount to use for the auto ack algorithm.
|
||||
* If stanzaCount is zero, then the number of stanzas will be ignored in the algorithm.
|
||||
*
|
||||
* @param timeout
|
||||
* The timeout to sue fo the auto ack algorithm.
|
||||
* If the timeout is zero (or negative), then the timer will be ignored in the algorithm.
|
||||
*
|
||||
* The default stanzaCount is 0 (disabled).
|
||||
* The default timeout is 0.0 seconds (disabled).
|
||||
**/
|
||||
- (void)automaticallySendAcksAfterStanzaCount:(NSUInteger)stanzaCount orTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
/**
|
||||
* Returns the current "auto-send unrequested acks" configuration.
|
||||
*
|
||||
* @see automaticallySendAcksAfterStanzaCount:orTimeout:
|
||||
**/
|
||||
- (void)getAutomaticallySendAcksAfterStanzaCount:(NSUInteger *)stanzaCountPtr orTimeout:(NSTimeInterval *)timeoutPtr;
|
||||
|
||||
/**
|
||||
* If an explicit request <r/> is received from the server, should we delay sending the ack <a/> ?
|
||||
* From XEP-0198 :
|
||||
*
|
||||
* > When an <r/> element ("request") is received, the recipient MUST acknowledge it by sending an <a/> element
|
||||
* > to the sender containing a value of 'h' that is equal to the number of stanzas handled by the recipient of
|
||||
* > the <r/> element. The response SHOULD be sent as soon as possible after receiving the <r/> element,
|
||||
* > and MUST NOT be withheld for any condition other than a timeout. For example, a client with a slow connection
|
||||
* > might want to collect many stanzas over a period of time before acking, and a server might want to throttle
|
||||
* > incoming stanzas.
|
||||
*
|
||||
* Thus the XEP recommends that you do not use a delay.
|
||||
* However, it acknowledges that there may be certain situations in which a delay could prove helpful.
|
||||
*
|
||||
* The default value is 0.0 (as recommended by XEP-0198)
|
||||
**/
|
||||
@property (atomic, assign, readwrite) NSTimeInterval ackResponseDelay;
|
||||
|
||||
/**
|
||||
* It's critically important to understand what an ACK means.
|
||||
*
|
||||
* Every ACK contains an 'h' attribute, which stands for "handled".
|
||||
* To paraphrase XEP-0198 (in client-side terminology):
|
||||
*
|
||||
* Acknowledging a previously received element indicates that the stanza has been "handled" by the client.
|
||||
* By "handled" we mean that the client has successfully processed the stanza
|
||||
* (including possibly saving the item to the database if needed);
|
||||
* Until a stanza has been affirmed as handled by the client, that stanza is the responsibility of the server
|
||||
* (e.g., to resend it or generate an error if it is never affirmed as handled by the client).
|
||||
*
|
||||
* This means that if your processing of certain elements includes saving them to a database,
|
||||
* then you should not mark those elements as handled until after your database has confirmed the data is on disk.
|
||||
*
|
||||
* You should note that this is a critical component of any networking app that claims to have "reliable messaging".
|
||||
*
|
||||
* By default, all elements will be marked as handled as soon as they arrive.
|
||||
* You'll want to override the default behavior for important elements that require proper handling by your app.
|
||||
* For example, messages that need to be saved to the database.
|
||||
* Here's how to do so:
|
||||
*
|
||||
* - Implement the delegate method xmppStreamManagement:getIsHandled:stanzaId:forReceivedElement:
|
||||
*
|
||||
* This method is invoked for all received elements.
|
||||
* You can inspect the element, and if it is important and requires special handling by the app,
|
||||
* then flag the element as NOT handled (overriding the default).
|
||||
* Also assign the element a "stanzaId". This can be anything you want, such as the elementID,
|
||||
* or maybe something more app-specific (e.g. something you already use that's associated with the message).
|
||||
*
|
||||
* - Handle the important element however you need to
|
||||
*
|
||||
* If you're saving something to the database,
|
||||
* then wait until after the database commit has completed successfully.
|
||||
*
|
||||
* - Notify the module that the element has been handled via the method markHandledStanzaId:
|
||||
*
|
||||
* You must pass the stanzaId that you returned from the delegate method.
|
||||
*
|
||||
*
|
||||
* @see xmppStreamManagement:getIsHandled:stanzaId:forReceivedElement:
|
||||
**/
|
||||
- (void)markHandledStanzaId:(id)stanzaId;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPStreamManagementDelegate
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Notifies delegates of the server's response from sending the <enable> stanza.
|
||||
**/
|
||||
- (void)xmppStreamManagement:(XMPPStreamManagement *)sender wasEnabled:(NSXMLElement *)enabled;
|
||||
- (void)xmppStreamManagement:(XMPPStreamManagement *)sender wasNotEnabled:(NSXMLElement *)failed;
|
||||
|
||||
/**
|
||||
* Notifies delegates that a request <r/> for an ack from the server was sent.
|
||||
**/
|
||||
- (void)xmppStreamManagementDidRequestAck:(XMPPStreamManagement *)sender;
|
||||
|
||||
/**
|
||||
* Invoked when an ack is received from the server, and new stanzas have been acked.
|
||||
*
|
||||
* @param stanzaIds
|
||||
* Includes all "stanzaIds" of sent elements that were just acked.
|
||||
*
|
||||
* What is a "stanzaId" ?
|
||||
*
|
||||
* A stanzaId is a unique identifier that ** YOU can provide ** in order to track an element.
|
||||
* It could simply be the elementId of the sent element. Or,
|
||||
* it could be something custom that you provide in order to properly lookup a message in your data store.
|
||||
*
|
||||
* For more information, see the delegate method xmppStreamManagement:stanzaIdForSentElement:
|
||||
**/
|
||||
- (void)xmppStreamManagement:(XMPPStreamManagement *)sender didReceiveAckForStanzaIds:(NSArray *)stanzaIds;
|
||||
|
||||
/**
|
||||
* XEP-0198 reports the following regarding duplicate stanzas:
|
||||
*
|
||||
* Because unacknowledged stanzas might have been received by the other party,
|
||||
* resending them might result in duplicates; there is no way to prevent such a
|
||||
* result in this protocol, although use of the XMPP 'id' attribute on all stanzas
|
||||
* can at least assist the intended recipients in weeding out duplicate stanzas.
|
||||
*
|
||||
* In other words, there are edge cases in which you might receive duplicates.
|
||||
* And the proper way to fix this is to use some kind of identifier in order to detect duplicates.
|
||||
*
|
||||
* What kind of identifier to use is up to you. (It's app specific.)
|
||||
* The XEP notes that you might use the 'id' attribute for this purpose. And this is certainly the most common case.
|
||||
* However, you may have an alternative scheme that works better for your purposes.
|
||||
* In which case you can use this delegate method to opt-in.
|
||||
*
|
||||
* For example:
|
||||
* You store all your messages in YapDatabase, which is a collection/key/value storage system.
|
||||
* Perhaps the collection is the conversationId, and the key is a messageId.
|
||||
* Therefore, to efficiently lookup a message in your datastore you'd prefer a collection/key tuple.
|
||||
*
|
||||
* To achieve this, you would implement this method, and return a YapCollectionKey object for message elements.
|
||||
* This way, when the xmppStreamManagement:didReceiveAckForStanzaIds: method is invoked,
|
||||
* you'll get a list that contains your collection/key tuple objects. And then you can quickly and efficiently
|
||||
* fetch and update your message objects.
|
||||
*
|
||||
* If there are no delegates that implement this method,
|
||||
* or all delegates return nil, then the stanza's elementId is used as the stanzaId.
|
||||
*
|
||||
* If the stanza isn't assigned a stanzaId (via a delegate method),
|
||||
* and it doesn't have an elementId, then it isn't reported in the acked stanzaIds array.
|
||||
**/
|
||||
- (id)xmppStreamManagement:(XMPPStreamManagement *)sender stanzaIdForSentElement:(XMPPElement *)element;
|
||||
|
||||
/**
|
||||
* It's critically important to understand what an ACK means.
|
||||
*
|
||||
* Every ACK contains an 'h' attribute, which stands for "handled".
|
||||
* To paraphrase XEP-0198 (in client-side terminology):
|
||||
*
|
||||
* Acknowledging a previously received element indicates that the stanza has been "handled" by the client.
|
||||
* By "handled" we mean that the client has successfully processed the stanza
|
||||
* (including possibly saving the item to the database if needed);
|
||||
* Until a stanza has been affirmed as handled by the client, that stanza is the responsibility of the server
|
||||
* (e.g., to resend it or generate an error if it is never affirmed as handled by the client).
|
||||
*
|
||||
* This means that if your processing of certain elements includes saving them to a database,
|
||||
* then you should not mark those elements as handled until after your database has confirmed the data is on disk.
|
||||
*
|
||||
* You should note that this is a critical component of any networking app that claims to have "reliable messaging".
|
||||
*
|
||||
* By default, all elements will be marked as handled as soon as they arrive.
|
||||
* You'll want to override the default behavior for important elements that require proper handling by your app.
|
||||
* For example, messages that need to be saved to the database.
|
||||
* Here's how to do so:
|
||||
*
|
||||
* - Implement the delegate method xmppStreamManagement:getIsHandled:stanzaId:forReceivedElement:
|
||||
*
|
||||
* This method is invoked for all received elements.
|
||||
* You can inspect the element, and if it is important and requires special handling by the app,
|
||||
* then flag the element as NOT handled (overriding the default).
|
||||
* Also assign the element a "stanzaId". This can be anything you want, such as the elementID,
|
||||
* or maybe something more app-specific (e.g. something you already use that's associated with the message).
|
||||
*
|
||||
* - Handle the important element however you need to
|
||||
*
|
||||
* If you're saving something to the database,
|
||||
* then wait until after the database commit has completed successfully.
|
||||
*
|
||||
* - Notify the module that the element has been handled via the method markHandledStanzaId:
|
||||
*
|
||||
* You must pass the stanzaId that you returned from this delegate method.
|
||||
*
|
||||
*
|
||||
* @see markHandledStanzaId:
|
||||
**/
|
||||
- (void)xmppStreamManagement:(XMPPStreamManagement *)sender
|
||||
getIsHandled:(BOOL *)isHandledPtr
|
||||
stanzaId:(id *)stanzaIdPtr
|
||||
forReceivedElement:(XMPPElement *)element;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPStreamManagementStorage <NSObject>
|
||||
@required
|
||||
|
||||
//
|
||||
//
|
||||
// -- PRIVATE METHODS --
|
||||
//
|
||||
// These methods are designed to be used ONLY by the XMPPStreamManagement class.
|
||||
//
|
||||
//
|
||||
|
||||
/**
|
||||
* Configures the storage class, passing it's parent and the parent's dispatch queue.
|
||||
*
|
||||
* This method is called by the init methods of the XMPPStreamManagement class.
|
||||
* This method is designed to inform the storage class of it's parent
|
||||
* and of the dispatch queue the parent will be operating on.
|
||||
*
|
||||
* A storage class may choose to operate on the same queue as it's parent,
|
||||
* as the majority of the time it will be getting called by the parent.
|
||||
* If both are operating on the same queue, the combination may run faster.
|
||||
*
|
||||
* Some storage classes support multiple xmppStreams,
|
||||
* and may choose to operate on their own internal queue.
|
||||
*
|
||||
* This method should return YES if it was configured properly.
|
||||
* It should return NO only if configuration failed.
|
||||
* For example, a storage class designed to be used only with a single xmppStream is being added to a second stream.
|
||||
**/
|
||||
- (BOOL)configureWithParent:(XMPPStreamManagement *)parent queue:(dispatch_queue_t)queue;
|
||||
|
||||
/**
|
||||
* Invoked after we receive <enabled/> from the server.
|
||||
*
|
||||
* @param resumptionId
|
||||
* The ID required to resume the session, given to us by the server.
|
||||
*
|
||||
* @param timeout
|
||||
* The timeout in seconds.
|
||||
* After a disconnect, the server will maintain our state for this long.
|
||||
* If we attempt to resume the session after this timeout it likely won't work.
|
||||
*
|
||||
* @param lastDisconnect
|
||||
* Used to reset the lastDisconnect value.
|
||||
* This value is often updated during the session, to ensure it closely resemble the date the server will use.
|
||||
* That is, if the client application is killed (or crashes) we want a relatively accurate lastDisconnect date.
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
*
|
||||
* This method should also nil out the following values (if needed) associated with the account:
|
||||
* - lastHandledByClient
|
||||
* - lastHandledByServer
|
||||
* - pendingOutgoingStanzas
|
||||
**/
|
||||
- (void)setResumptionId:(NSString *)resumptionId
|
||||
timeout:(uint32_t)timeout
|
||||
lastDisconnect:(NSDate *)date
|
||||
forStream:(XMPPStream *)stream;
|
||||
|
||||
/**
|
||||
* This method is invoked ** often ** during stream operation.
|
||||
* It is not invoked when the xmppStream is disconnected.
|
||||
*
|
||||
* Important: See the note below: "Optimizing storage demands during active stream usage"
|
||||
*
|
||||
* @param date
|
||||
* Updates the previous lastDisconnect value.
|
||||
*
|
||||
* @param lastHandledByClient
|
||||
* The most recent 'h' value we can safely send to the server.
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
**/
|
||||
- (void)setLastDisconnect:(NSDate *)date
|
||||
lastHandledByClient:(uint32_t)lastHandledByClient
|
||||
forStream:(XMPPStream *)stream;
|
||||
|
||||
/**
|
||||
* This method is invoked ** often ** during stream operation.
|
||||
* It is not invoked when the xmppStream is disconnected.
|
||||
*
|
||||
* Important: See the note below: "Optimizing storage demands during active stream usage"
|
||||
*
|
||||
* @param date
|
||||
* Updates the previous lastDisconnect value.
|
||||
*
|
||||
* @param lastHandledByServer
|
||||
* The most recent 'h' value we've received from the server.
|
||||
*
|
||||
* @param pendingOutgoingStanzas
|
||||
* An array of XMPPStreamManagementOutgoingStanza objects.
|
||||
* The storage layer is in charge of properly persisting this array, including:
|
||||
* - the array count
|
||||
* - the stanzaId of each element, including those that are nil
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
**/
|
||||
- (void)setLastDisconnect:(NSDate *)date
|
||||
lastHandledByServer:(uint32_t)lastHandledByServer
|
||||
pendingOutgoingStanzas:(NSArray *)pendingOutgoingStanzas
|
||||
forStream:(XMPPStream *)stream;
|
||||
|
||||
|
||||
/// ***** Optimizing storage demands during active stream usage *****
|
||||
///
|
||||
/// There are 2 methods that are invoked frequently during stream activity:
|
||||
///
|
||||
/// - setLastDisconnect:lastHandledByClient:forStream:
|
||||
/// - setLastDisconnect:lastHandledByServer:pendingOutgoingStanzas:forStream:
|
||||
///
|
||||
/// They are invoked any time the 'h' values change, or whenver the pendingStanzaIds change.
|
||||
/// In other words, they are invoked continually as stanzas get sent and received.
|
||||
/// And it is the job of the storage layer to decide how to handle the traffic.
|
||||
/// There are a few things to consider here:
|
||||
///
|
||||
/// - How much chatter does the xmppStream do?
|
||||
/// - How fast is the storage layer?
|
||||
/// - How does the overhead on the storage layer affect the rest of the app?
|
||||
///
|
||||
/// If your xmppStream isn't very chatty, and you've got a fast concurrent database,
|
||||
/// then you may be able to simply pipe all these method calls to the database without thinking.
|
||||
/// However, if your xmppStream is always constantly sending/receiving presence stanzas, and pinging the server,
|
||||
/// then you might consider a bit of optimzation here. Below is a simple recommendation for how to accomplish this.
|
||||
///
|
||||
/// You could choose to queue the changes from these method calls, and dump them to the database after a timeout.
|
||||
/// Thus you'll be able to consolidate a large traffic surge into a small handful of database operations.
|
||||
///
|
||||
/// Also, you could expose a 'flush' operation on the storage layer.
|
||||
/// And invoke the flush operation when the app is backgrounded, or about to quit.
|
||||
|
||||
|
||||
/**
|
||||
* This method is invoked immediately after an accidental disconnect.
|
||||
* And may be invoked post-disconnect if the state changes, such as for the following edge cases:
|
||||
*
|
||||
* - due to continued processing of stanzas received pre-disconnect,
|
||||
* that are just now being marked as handled by the delegate(s)
|
||||
* - due to a delayed response from the delegate(s),
|
||||
* such that we didn't receive the stanzaId for an outgoing stanza until after the disconnect occurred.
|
||||
*
|
||||
* This method is not invoked if stream management is started on a connected xmppStream.
|
||||
*
|
||||
* @param date
|
||||
* This value will be the actual disconnect date.
|
||||
*
|
||||
* @param lastHandledByClient
|
||||
* The most recent 'h' value we can safely send to the server.
|
||||
*
|
||||
* @param lastHandledByServer
|
||||
* The most recent 'h' value we've received from the server.
|
||||
*
|
||||
* @param pendingOutgoingStanzas
|
||||
* An array of XMPPStreamManagementOutgoingStanza objects.
|
||||
* The storage layer is in charge of properly persisting this array, including:
|
||||
* - the array count
|
||||
* - the stanzaId of each element, including those that are nil
|
||||
*
|
||||
* @param stream
|
||||
* The associated xmppStream (standard parameter for storage classes)
|
||||
**/
|
||||
- (void)setLastDisconnect:(NSDate *)date
|
||||
lastHandledByClient:(uint32_t)lastHandledByClient
|
||||
lastHandledByServer:(uint32_t)lastHandledByServer
|
||||
pendingOutgoingStanzas:(NSArray *)pendingOutgoingStanzas
|
||||
forStream:(XMPPStream *)stream;
|
||||
|
||||
/**
|
||||
* Invoked when the extension needs values from a previous session.
|
||||
* This method is used to get values needed in order to determine if it can resume a previous stream.
|
||||
**/
|
||||
- (void)getResumptionId:(NSString **)resumptionIdPtr
|
||||
timeout:(uint32_t *)timeoutPtr
|
||||
lastDisconnect:(NSDate **)lastDisconnectPtr
|
||||
forStream:(XMPPStream *)stream;
|
||||
|
||||
/**
|
||||
* Invoked when the extension needs values from a previous session.
|
||||
* This method is used to get values needed in order to resume a previous stream.
|
||||
**/
|
||||
- (void)getLastHandledByClient:(uint32_t *)lastHandledByClientPtr
|
||||
lastHandledByServer:(uint32_t *)lastHandledByServerPtr
|
||||
pendingOutgoingStanzas:(NSArray **)pendingOutgoingStanzasPtr
|
||||
forStream:(XMPPStream *)stream;
|
||||
|
||||
/**
|
||||
* Instructs the storage layer to remove all values stored for the given stream.
|
||||
* This occurs after the extension detects a "cleanly closed stream",
|
||||
* in which case the stream cannot be resumed next time.
|
||||
**/
|
||||
- (void)removeAllForStream:(XMPPStream *)stream;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPStream (XMPPStreamManagement)
|
||||
|
||||
/**
|
||||
* Returns whether or not the server's <stream:features> includes <sm xmlns='urn:xmpp:sm:3'/>.
|
||||
**/
|
||||
- (BOOL)supportsStreamManagement;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user