Libraries/QServer.h

/*
    File:       QServer.h
 
    Contains:   A generic TCP server object.
 
    Written by: DTS
 
    Copyright:  Copyright (c) 2011 Apple Inc. All Rights Reserved.
 
    Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
                ("Apple") in consideration of your agreement to the following
                terms, and your use, installation, modification or
                redistribution of this Apple software constitutes acceptance of
                these terms.  If you do not agree with these terms, please do
                not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following
                terms, and subject to these terms, Apple grants you a personal,
                non-exclusive license, under Apple's copyrights in this
                original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or
                without modifications, in source and/or binary forms; provided
                that if you redistribute the Apple Software in its entirety and
                without modifications, you must retain this notice and the
                following text and disclaimers in all such redistributions of
                the Apple Software. Neither the name, trademarks, service marks
                or logos of Apple Inc. may be used to endorse or promote
                products derived from the Apple Software without specific prior
                written permission from Apple.  Except as expressly stated in
                this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any
                patent rights that may be infringed by your derivative works or
                by other works in which the Apple Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis. 
                APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
                WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
                MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
                THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
                INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
                TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
                DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
                OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
                OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
                OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
                SUCH DAMAGE.
 
*/
 
#import <Foundation/Foundation.h>
 
// QServer is a general purpose class that starts a TCP server and listens for 
// incoming connections.  It is intended to be used in one of two ways:
//
// o With a fixed port number, as is traditional for TCP servers.
// o With a dynamic port number which is registered with Bonjour.
//
// QServer does the right thing by default in a number of cases:
// 
// o It supports IPv6 if it's available (can be disabled for testing).
// o It supports automatic Bonjour service renaming.
// o If you have a fixed port and register with Bonjour, it automatically 
//   backs off to a dynamic port if the fixed port is not available.
// 
// The class is run loop based and must be called from a single thread. 
// Specifically, the -start and -stop methods add and remove run loop sources 
// to the current thread's run loop, and it's that thread that calls the 
// delegate callbacks.
//
// You typically use this class by:
// 
// o allocating and hold on to a QServer object
// o setting the delegate
// o calling -start
// o implementing the -server:connectionForInputStream:outputStream: 
//   to create a new connection object to handle a connection over the input and 
//   output streams
// o implementation -server:closeConnection: to handle the case where the server 
//   force closes that connection (because the server was stopped)
 
@protocol QServerDelegate;
 
@interface QServer : NSObject
 
- (id)initWithDomain:(NSString *)domain type:(NSString *)type name:(NSString *)name preferredPort:(NSUInteger)preferredPort;
    // Initialise the server object.  This does not actually start the server; call 
    // -start to do that.
    //
    // If type is nil, the server is not registered with Bonjour.
    // If type is not nil, the server is registered in the specified 
    // domain with the specified name.  A domain of nil is equivalent 
    // to @"", that is, all standard domains.  A name of nil is equivalent 
    // to @"", that is, the service is given the default name.
    // 
    // If preferredPort is 0, a dynamic port is used, otherwise preferredPort is 
    // used if it's not busy.  If it busy, and no type is set, the server fails 
    // to start.  OTOH, if type is set, the server backs off to using a dynamic port 
    // (the logic being that, if it's advertised with Bonjour, clients will stil 
    // be able to find it).
 
// properties set by the init method
 
@property (nonatomic, copy,   readonly ) NSString *             domain;         // immutable, what you passed in to -initXxx
@property (nonatomic, copy,   readonly ) NSString *             type;           // immutable, what you passed in to -initXxx
@property (nonatomic, copy,   readonly ) NSString *             name;           // immutable, what you passed in to -initXxx
@property (nonatomic, assign, readonly ) NSUInteger             preferredPort;  // mutable, but only effective on the next -start
 
// properties you can configure, but not between a -start and -stop
 
@property (nonatomic, assign, readwrite) BOOL                   disableIPv6;    // primarily for testing purposes, default is NO, only effective on next -start
 
// properties you can configure at any time
 
@property (nonatomic, assign, readwrite) id<QServerDelegate>    delegate;
 
// properties that change as the result of other actions
 
@property (nonatomic, assign, readonly ) NSUInteger             connectionSequenceNumber;       // observable
    // This increments each time a connection is made.  It's primarily for debugging purposes.
 
#pragma mark * Start and Stop
 
// It is reasonable to start and stop the same server object multiple times.
 
- (void)start;
    // Starts the server.  It's not legal to call this if the server is started.  
    // If startup attempted, will eventually call -serverDidStart: or 
    // -server:didStopWithError:.
 
- (void)stop;
    // Does nothing if the server is already stopped.
    // This does not call -server:didStopWithError:.
    // This /does/ call -server:didStopConnection: for each running connection.
 
@property (nonatomic, assign, readonly, getter=isStarted) BOOL  started;        // observable
@property (nonatomic, assign, readonly ) NSUInteger             registeredPort; // observable, only meaningful if isStarted is YES
@property (nonatomic, copy,   readonly ) NSString *             registeredName; // observable, only meaningful if isStarted is YES, 
                                                                                // may change due to Bonjour auto renaming, 
                                                                                // will be nil if Bonjour registration not requested, 
                                                                                // may be nil if Bonjour registration in progress
 
#pragma mark * Connections
 
- (void)closeOneConnection:(id)connection;
    // Remove a connection from the connections set.  This does /not/ call the 
    // -server:closeConnection: delegate method for the connection.  A connection can 
    // can call this on itself.  Does nothing if the connection not in the connections set.
 
- (void)closeAllConnections;
    // Closes all connections known to the server.  This /does/ call (synchronously) 
    // -server:closeConnection: on each connection.
 
@property (nonatomic, copy,   readonly ) NSSet *                connections;
 
#pragma mark * Run Loop Modes
 
// You can't add or remove run loop modes while the server is running.
 
- (void)addRunLoopMode:(NSString *)modeToAdd;
- (void)removeRunLoopMode:(NSString *)modeToRemove;
 
@property (nonatomic, copy,   readonly ) NSSet *                runLoopModes;   // contains NSDefaultRunLoopMode by default
 
// The following are utility methods that allow you to easily schedule streams in 
// the same run loop modes as the server.
 
- (void)scheduleInRunLoopModesInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream;
- (void)removeFromRunLoopModesInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream;
    // One of inputStream or outputStream may be nil.
 
@end
 
 
@protocol QServerDelegate <NSObject>
 
@optional
 
- (void)serverDidStart:(QServer *)server;
    // Called after the server has fully started, that is, once the Bonjour name 
    // registration (if requested) is complete.  You can use registeredName to get 
    // the actual service name that was registered.
    
- (void)server:(QServer *)server didStopWithError:(NSError *)error;
    // Called when the server stops of its own accord, typically in response to some 
    // horrible network problem.
 
// You should implement one and only one of the following callbacks.  If you implement 
// both, -server:connectionForSocket: is called.
 
- (id)server:(QServer *)server connectionForSocket:(int)fd;
    // Called to get a connection object for a new, incoming connection.  If you don't implement 
    // this, or you return nil, the socket for the connection is just closed.  If you do return 
    // a connection object, you are responsible for holding on to socket and ensuring that it's 
    // closed on the -server:closeConnection: delegate callback.
 
- (id)server:(QServer *)server connectionForInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream;
    // Called to get a connection object for a new, incoming connection.  If you don't implement 
    // this, or you return nil, the incoming connection is just closed.  If you do return a 
    // connection object, you are responsible for opening the two streams (or just one of the 
    // streams, if you only need one), holding on to them, and ensuring that they are closed 
    // and released on the -server:closeConnection delegate callback.
 
- (void)server:(QServer *)server closeConnection:(id)connection;
    // Called when the server shuts down or if someone calls -closeAllConnections. 
    // Typically the delegate would just forward this call to the connection object itself.
 
- (void)server:(QServer *)server logWithFormat:(NSString *)format arguments:(va_list)argList;
    // Called to log server activity.
 
@end
 
 
@interface QServer (ForSubclassers)
 
// The methods in this category are for subclassers only.  Client code would not 
// be expected to use any of this.
 
// QServer uses a reasonable default algorithm for binding its listening sockets. 
// Specifically:
//
// A. IPv4 is always enabled.  IPv6 is enabled if it's present on the system and is 
//    not explicitly disabled using disableIPv6.
// B. It always binds IPv4 and IPv6 (if enabled) to the same port.
// C. If preferredPort is zero, binding is very likely to succeed with registeredPort set to 
//    some dynamic port.
// D. If preferredPort is non-zero and type is nil, it either binds to the preferred 
//    port or fails to start up.
// E. If preferredPort is non-zero and type is set, it first tries to bind to the preferred 
//    port but, if that fails, uses a dynamic port.
//
// If this algorithm is not appropriate for your application you can override it by 
// subclassing QServer and overriding the -listenOnPortError: method.  Any listening 
// sockets that you create should be registered with run loop by calling -addListeningSocket:.
 
- (NSUInteger)listenOnPortError:(NSError **)errorPtr;
    // Override this method to change the binding algorithm used by QServer.  The method 
    // must return the port number to which you bound (all listening sockets must be bound 
    // to the same port lest you confuse Bonjour).  If it returns 0 and errorPtr is not NULL, 
    // then *errorPtr must be an NSError indicating the reason for the failure.
 
- (void)addListeningSocket:(int)fd;
    // Adds the specified listening socket to the run loop (well, to the set of sockets 
    // that get added to the run loop when you start the server).  You should only call 
    // this from a -listenOnPortError: override.
 
// The following methods allow subclasses to see the delegate methods without actually 
// being the delegate.  The default implementation of these routines just calls the 
// delegate callback, if any (except for -connectionForSocket:, which does the 
// -server:connectionForSocket: / -server:connectionForInputStream:outputStream: dance; 
// see the code for the details).
 
- (void)didStart;
- (void)didStopWithError:(NSError *)error;
- (id)connectionForSocket:(int)fd;
// There is no -connectionForInputStream:outputStream:.  A subclasser must override 
// -connectionForSocket:.
//
// -(id)connectionForInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream;
- (void)closeConnection:(id)connection;
- (void)logWithFormat:(NSString *)format arguments:(va_list)argList;
 
@end