PNXMPPFramework/Extensions/XEP-0198/XMPPStreamManagement.h
2016-02-24 16:56:39 +01:00

618 lines
27 KiB
Objective-C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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