XMPPStreamManagement.h 26.7 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 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