/* |
File: UDPEcho.m |
|
Contains: A class that implements a UDP echo protocol client and server. |
|
Written by: DTS |
|
Copyright: Copyright (c) 2010-12 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. |
|
*/ |
|
#if ! defined(UDPECHO_IPV4_ONLY) |
#define UDPECHO_IPV4_ONLY 0 |
#endif |
|
#import "UDPEcho.h" |
|
#include <sys/socket.h> |
#include <netinet/in.h> |
#include <fcntl.h> |
#include <unistd.h> |
|
@interface UDPEcho () |
|
// redeclare as readwrite for private use |
|
@property (nonatomic, copy, readwrite) NSString * hostName; |
@property (nonatomic, copy, readwrite) NSData * hostAddress; |
@property (nonatomic, assign, readwrite) NSUInteger port; |
|
// forward declarations |
|
- (void)stopHostResolution; |
- (void)stopWithError:(NSError *)error; |
- (void)stopWithStreamError:(CFStreamError)streamError; |
|
@end |
|
@implementation UDPEcho |
{ |
CFHostRef _cfHost; |
CFSocketRef _cfSocket; |
} |
|
@synthesize delegate = _delegate; |
@synthesize hostName = _hostName; |
@synthesize hostAddress = _hostAddress; |
@synthesize port = _port; |
|
- (id)init |
{ |
self = [super init]; |
if (self != nil) { |
// do nothing |
} |
return self; |
} |
|
- (void)dealloc |
{ |
[self stop]; |
} |
|
- (BOOL)isServer |
{ |
return self.hostName == nil; |
} |
|
- (void)sendData:(NSData *)data toAddress:(NSData *)addr |
// Called by both -sendData: and the server echoing code to send data |
// via the socket. addr is nil in the client case, whereupon the |
// data is automatically sent to the hostAddress by virtue of the fact |
// that the socket is connected to that address. |
{ |
int err; |
int sock; |
ssize_t bytesWritten; |
const struct sockaddr * addrPtr; |
socklen_t addrLen; |
|
assert(data != nil); |
assert( (addr != nil) == self.isServer ); |
assert( (addr == nil) || ([addr length] <= sizeof(struct sockaddr_storage)) ); |
|
sock = CFSocketGetNative(self->_cfSocket); |
assert(sock >= 0); |
|
if (addr == nil) { |
addr = self.hostAddress; |
assert(addr != nil); |
addrPtr = NULL; |
addrLen = 0; |
} else { |
addrPtr = [addr bytes]; |
addrLen = (socklen_t) [addr length]; |
} |
|
bytesWritten = sendto(sock, [data bytes], [data length], 0, addrPtr, addrLen); |
if (bytesWritten < 0) { |
err = errno; |
} else if (bytesWritten == 0) { |
err = EPIPE; |
} else { |
// We ignore any short writes, which shouldn't happen for UDP anyway. |
assert( (NSUInteger) bytesWritten == [data length] ); |
err = 0; |
} |
|
if (err == 0) { |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didSendData:toAddress:)] ) { |
[self.delegate echo:self didSendData:data toAddress:addr]; |
} |
} else { |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didFailToSendData:toAddress:error:)] ) { |
[self.delegate echo:self didFailToSendData:data toAddress:addr error:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; |
} |
} |
} |
|
- (void)readData |
// Called by the CFSocket read callback to actually read and process data |
// from the socket. |
{ |
int err; |
int sock; |
struct sockaddr_storage addr; |
socklen_t addrLen; |
uint8_t buffer[65536]; |
ssize_t bytesRead; |
|
sock = CFSocketGetNative(self->_cfSocket); |
assert(sock >= 0); |
|
addrLen = sizeof(addr); |
bytesRead = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &addrLen); |
if (bytesRead < 0) { |
err = errno; |
} else if (bytesRead == 0) { |
err = EPIPE; |
} else { |
NSData * dataObj; |
NSData * addrObj; |
|
err = 0; |
|
dataObj = [NSData dataWithBytes:buffer length:(NSUInteger) bytesRead]; |
assert(dataObj != nil); |
addrObj = [NSData dataWithBytes:&addr length:addrLen ]; |
assert(addrObj != nil); |
|
// Tell the delegate about the data. |
|
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didReceiveData:fromAddress:)] ) { |
[self.delegate echo:self didReceiveData:dataObj fromAddress:addrObj]; |
} |
|
// Echo the data back to the sender. |
|
if (self.isServer) { |
[self sendData:dataObj toAddress:addrObj]; |
} |
} |
|
// If we got an error, tell the delegate. |
|
if (err != 0) { |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didReceiveError:)] ) { |
[self.delegate echo:self didReceiveError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]]; |
} |
} |
} |
|
static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) |
// This C routine is called by CFSocket when there's data waiting on our |
// UDP socket. It just redirects the call to Objective-C code. |
{ |
UDPEcho * obj; |
|
obj = (__bridge UDPEcho *) info; |
assert([obj isKindOfClass:[UDPEcho class]]); |
|
#pragma unused(s) |
assert(s == obj->_cfSocket); |
#pragma unused(type) |
assert(type == kCFSocketReadCallBack); |
#pragma unused(address) |
assert(address == nil); |
#pragma unused(data) |
assert(data == nil); |
|
[obj readData]; |
} |
|
#if UDPECHO_IPV4_ONLY |
|
- (BOOL)setupSocketConnectedToAddress:(NSData *)address port:(NSUInteger)port error:(NSError **)errorPtr |
// Sets up the CFSocket in either client or server mode. In client mode, |
// address contains the address that the socket should be connected to. |
// The address contains zero port number, so the port parameter is used instead. |
// In server mode, address is nil and the socket is bound to the wildcard |
// address on the specified port. |
{ |
int err; |
int junk; |
int sock; |
const CFSocketContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL }; |
CFRunLoopSourceRef rls; |
|
assert( (address == nil) == self.isServer ); |
assert( (address == nil) || ([address length] <= sizeof(struct sockaddr_storage)) ); |
assert(port < 65536); |
|
assert(self->_cfSocket == NULL); |
|
// Create the UDP socket itself. |
|
err = 0; |
sock = socket(AF_INET, SOCK_DGRAM, 0); |
if (sock < 0) { |
err = errno; |
} |
|
// Bind or connect the socket, depending on whether we're in server or client mode. |
|
if (err == 0) { |
struct sockaddr_in addr; |
|
memset(&addr, 0, sizeof(addr)); |
if (address == nil) { |
// Server mode. Set up the address based on the socket family of the socket |
// that we created, with the wildcard address and the caller-supplied port number. |
addr.sin_len = sizeof(addr); |
addr.sin_family = AF_INET; |
addr.sin_port = htons(port); |
addr.sin_addr.s_addr = INADDR_ANY; |
err = bind(sock, (const struct sockaddr *) &addr, sizeof(addr)); |
} else { |
// Client mode. Set up the address on the caller-supplied address and port |
// number. |
if ([address length] > sizeof(addr)) { |
assert(NO); // very weird |
[address getBytes:&addr length:sizeof(addr)]; |
} else { |
[address getBytes:&addr length:[address length]]; |
} |
assert(addr.sin_family == AF_INET); |
addr.sin_port = htons(port); |
err = connect(sock, (const struct sockaddr *) &addr, sizeof(addr)); |
} |
if (err < 0) { |
err = errno; |
} |
} |
|
// From now on we want the socket in non-blocking mode to prevent any unexpected |
// blocking of the main thread. None of the above should block for any meaningful |
// amount of time. |
|
if (err == 0) { |
int flags; |
|
flags = fcntl(sock, F_GETFL); |
err = fcntl(sock, F_SETFL, flags | O_NONBLOCK); |
if (err < 0) { |
err = errno; |
} |
} |
|
// Wrap the socket in a CFSocket that's scheduled on the runloop. |
|
if (err == 0) { |
self->_cfSocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, SocketReadCallback, &context); |
|
// The socket will now take care of cleaning up our file descriptor. |
|
assert( CFSocketGetSocketFlags(self->_cfSocket) & kCFSocketCloseOnInvalidate ); |
sock = -1; |
|
rls = CFSocketCreateRunLoopSource(NULL, self->_cfSocket, 0); |
assert(rls != NULL); |
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); |
|
CFRelease(rls); |
} |
|
// Handle any errors. |
|
if (sock != -1) { |
junk = close(sock); |
assert(junk == 0); |
} |
assert( (err == 0) == (self->_cfSocket != NULL) ); |
if ( (self->_cfSocket == NULL) && (errorPtr != NULL) ) { |
*errorPtr = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; |
} |
|
return (err == 0); |
} |
|
#else // ! UDPECHO_IPV4_ONLY |
|
- (BOOL)setupSocketConnectedToAddress:(NSData *)address port:(NSUInteger)port error:(NSError **)errorPtr |
// Sets up the CFSocket in either client or server mode. In client mode, |
// address contains the address that the socket should be connected to. |
// The address contains zero port number, so the port parameter is used instead. |
// In server mode, address is nil and the socket is bound to the wildcard |
// address on the specified port. |
{ |
sa_family_t socketFamily; |
int err; |
int junk; |
int sock; |
const CFSocketContext context = { 0, (__bridge void *) (self), NULL, NULL, NULL }; |
CFRunLoopSourceRef rls; |
|
assert( (address == nil) == self.isServer ); |
assert( (address == nil) || ([address length] <= sizeof(struct sockaddr_storage)) ); |
assert(port < 65536); |
|
assert(self->_cfSocket == NULL); |
|
// Create the UDP socket itself. First try IPv6 and, if that's not available, revert to IPv6. |
// |
// IMPORTANT: Even though we're using IPv6 by default, we can still work with IPv4 due to the |
// miracle of IPv4-mapped addresses. |
|
err = 0; |
sock = socket(AF_INET6, SOCK_DGRAM, 0); |
if (sock >= 0) { |
socketFamily = AF_INET6; |
} else { |
sock = socket(AF_INET, SOCK_DGRAM, 0); |
if (sock >= 0) { |
socketFamily = AF_INET; |
} else { |
err = errno; |
socketFamily = 0; // quietens a warning from the compiler |
assert(err != 0); // Obvious, but it quietens a warning from the static analyser. |
} |
} |
|
// Bind or connect the socket, depending on whether we're in server or client mode. |
|
if (err == 0) { |
struct sockaddr_storage addr; |
struct sockaddr_in * addr4; |
struct sockaddr_in6 * addr6; |
|
addr4 = (struct sockaddr_in * ) &addr; |
addr6 = (struct sockaddr_in6 *) &addr; |
|
memset(&addr, 0, sizeof(addr)); |
if (address == nil) { |
// Server mode. Set up the address based on the socket family of the socket |
// that we created, with the wildcard address and the caller-supplied port number. |
addr.ss_family = socketFamily; |
if (socketFamily == AF_INET) { |
addr4->sin_len = sizeof(*addr4); |
addr4->sin_port = htons(port); |
addr4->sin_addr.s_addr = INADDR_ANY; |
} else { |
assert(socketFamily == AF_INET6); |
addr6->sin6_len = sizeof(*addr6); |
addr6->sin6_port = htons(port); |
addr6->sin6_addr = in6addr_any; |
} |
} else { |
// Client mode. Set up the address on the caller-supplied address and port |
// number. Also, if the address is IPv4 and we created an IPv6 socket, |
// convert the address to an IPv4-mapped address. |
if ([address length] > sizeof(addr)) { |
assert(NO); // very weird |
[address getBytes:&addr length:sizeof(addr)]; |
} else { |
[address getBytes:&addr length:[address length]]; |
} |
if (addr.ss_family == AF_INET) { |
if (socketFamily == AF_INET6) { |
struct in_addr ipv4Addr; |
|
// Convert IPv4 address to IPv4-mapped-into-IPv6 address. |
|
ipv4Addr = addr4->sin_addr; |
|
addr6->sin6_len = sizeof(*addr6); |
addr6->sin6_family = AF_INET6; |
addr6->sin6_port = htons(port); |
addr6->sin6_addr.__u6_addr.__u6_addr32[0] = 0; |
addr6->sin6_addr.__u6_addr.__u6_addr32[1] = 0; |
addr6->sin6_addr.__u6_addr.__u6_addr16[4] = 0; |
addr6->sin6_addr.__u6_addr.__u6_addr16[5] = 0xffff; |
addr6->sin6_addr.__u6_addr.__u6_addr32[3] = ipv4Addr.s_addr; |
} else { |
addr4->sin_port = htons(port); |
} |
} else { |
assert(addr.ss_family == AF_INET6); |
addr6->sin6_port = htons(port); |
} |
if ( (addr.ss_family == AF_INET) && (socketFamily == AF_INET6) ) { |
addr6->sin6_len = sizeof(*addr6); |
addr6->sin6_port = htons(port); |
addr6->sin6_addr = in6addr_any; |
} |
} |
if (address == nil) { |
err = bind(sock, (const struct sockaddr *) &addr, addr.ss_len); |
} else { |
err = connect(sock, (const struct sockaddr *) &addr, addr.ss_len); |
} |
if (err < 0) { |
err = errno; |
} |
} |
|
// From now on we want the socket in non-blocking mode to prevent any unexpected |
// blocking of the main thread. None of the above should block for any meaningful |
// amount of time. |
|
if (err == 0) { |
int flags; |
|
flags = fcntl(sock, F_GETFL); |
err = fcntl(sock, F_SETFL, flags | O_NONBLOCK); |
if (err < 0) { |
err = errno; |
} |
} |
|
// Wrap the socket in a CFSocket that's scheduled on the runloop. |
|
if (err == 0) { |
self->_cfSocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, SocketReadCallback, &context); |
|
// The socket will now take care of cleaning up our file descriptor. |
|
assert( CFSocketGetSocketFlags(self->_cfSocket) & kCFSocketCloseOnInvalidate ); |
sock = -1; |
|
rls = CFSocketCreateRunLoopSource(NULL, self->_cfSocket, 0); |
assert(rls != NULL); |
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); |
|
CFRelease(rls); |
} |
|
// Handle any errors. |
|
if (sock != -1) { |
junk = close(sock); |
assert(junk == 0); |
} |
assert( (err == 0) == (self->_cfSocket != NULL) ); |
if ( (self->_cfSocket == NULL) && (errorPtr != NULL) ) { |
*errorPtr = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; |
} |
|
return (err == 0); |
} |
|
#endif // ! UDPECHO_IPV4_ONLY |
|
- (void)startServerOnPort:(NSUInteger)port |
// See comment in header. |
{ |
assert( (port > 0) && (port < 65536) ); |
|
assert(self.port == 0); // don't try and start a started object |
if (self.port == 0) { |
BOOL success; |
NSError * error; |
|
// Create a fully configured socket. |
|
success = [self setupSocketConnectedToAddress:nil port:port error:&error]; |
|
// If we can create the socket, we're good to go. Otherwise, we report an error |
// to the delegate. |
|
if (success) { |
self.port = port; |
|
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didStartWithAddress:)] ) { |
CFDataRef localAddress; |
|
localAddress = CFSocketCopyAddress(self->_cfSocket); |
assert(localAddress != NULL); |
|
[self.delegate echo:self didStartWithAddress:(__bridge NSData *) localAddress]; |
|
CFRelease(localAddress); |
} |
} else { |
[self stopWithError:error]; |
} |
} |
} |
|
- (void)hostResolutionDone |
// Called by our CFHost resolution callback (HostResolveCallback) when host |
// resolution is complete. We find the best IP address and create a socket |
// connected to that. |
{ |
NSError * error; |
Boolean resolved; |
NSArray * resolvedAddresses; |
|
assert(self.port != 0); |
assert(self->_cfHost != NULL); |
assert(self->_cfSocket == NULL); |
assert(self.hostAddress == nil); |
|
error = nil; |
|
// Walk through the resolved addresses looking for one that we can work with. |
|
resolvedAddresses = (__bridge NSArray *) CFHostGetAddressing(self->_cfHost, &resolved); |
if ( resolved && (resolvedAddresses != nil) ) { |
for (NSData * address in resolvedAddresses) { |
BOOL success; |
const struct sockaddr * addrPtr; |
NSUInteger addrLen; |
|
addrPtr = (const struct sockaddr *) [address bytes]; |
addrLen = [address length]; |
assert(addrLen >= sizeof(struct sockaddr)); |
|
// Try to create a connected CFSocket for this address. If that fails, |
// we move along to the next address. If it succeeds, we're done. |
|
success = NO; |
if ( |
(addrPtr->sa_family == AF_INET) |
#if ! UDPECHO_IPV4_ONLY |
|| (addrPtr->sa_family == AF_INET6) |
#endif |
) { |
success = [self setupSocketConnectedToAddress:address port:self.port error:&error]; |
if (success) { |
CFDataRef hostAddress; |
|
hostAddress = CFSocketCopyPeerAddress(self->_cfSocket); |
assert(hostAddress != NULL); |
|
self.hostAddress = (__bridge NSData *) hostAddress; |
|
CFRelease(hostAddress); |
} |
} |
if (success) { |
break; |
} |
} |
} |
|
// If we didn't get an address and didn't get an error, synthesise a host not found error. |
|
if ( (self.hostAddress == nil) && (error == nil) ) { |
error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]; |
} |
|
if (error == nil) { |
// We're done resolving, so shut that down. |
|
[self stopHostResolution]; |
|
// Tell the delegate that we're up. |
|
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didStartWithAddress:)] ) { |
[self.delegate echo:self didStartWithAddress:self.hostAddress]; |
} |
} else { |
[self stopWithError:error]; |
} |
} |
|
static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) |
// This C routine is called by CFHost when the host resolution is complete. |
// It just redirects the call to the appropriate Objective-C method. |
{ |
UDPEcho * obj; |
|
obj = (__bridge UDPEcho *) info; |
assert([obj isKindOfClass:[UDPEcho class]]); |
|
#pragma unused(theHost) |
assert(theHost == obj->_cfHost); |
#pragma unused(typeInfo) |
assert(typeInfo == kCFHostAddresses); |
|
if ( (error != NULL) && (error->domain != 0) ) { |
[obj stopWithStreamError:*error]; |
} else { |
[obj hostResolutionDone]; |
} |
} |
|
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port |
// See comment in header. |
{ |
assert(hostName != nil); |
assert( (port > 0) && (port < 65536) ); |
|
assert(self.port == 0); // don't try and start a started object |
if (self.port == 0) { |
Boolean success; |
CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; |
CFStreamError streamError; |
|
assert(self->_cfHost == NULL); |
|
self->_cfHost = CFHostCreateWithName(NULL, (__bridge CFStringRef) hostName); |
assert(self->_cfHost != NULL); |
|
CFHostSetClient(self->_cfHost, HostResolveCallback, &context); |
|
CFHostScheduleWithRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
|
success = CFHostStartInfoResolution(self->_cfHost, kCFHostAddresses, &streamError); |
if (success) { |
self.hostName = hostName; |
self.port = port; |
// ... continue in HostResolveCallback |
} else { |
[self stopWithStreamError:streamError]; |
} |
} |
} |
|
- (void)sendData:(NSData *)data |
// See comment in header. |
{ |
// If you call -sendData: on a object in server mode or an object in client mode |
// that's not fully set up (hostAddress is nil), we just ignore you. |
if (self.isServer || (self.hostAddress == nil) ) { |
assert(NO); |
} else { |
[self sendData:data toAddress:nil]; |
} |
} |
|
- (void)stopHostResolution |
// Called to stop the CFHost part of the object, if it's still running. |
{ |
if (self->_cfHost != NULL) { |
CFHostSetClient(self->_cfHost, NULL, NULL); |
CFHostCancelInfoResolution(self->_cfHost, kCFHostAddresses); |
CFHostUnscheduleFromRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
CFRelease(self->_cfHost); |
self->_cfHost = NULL; |
} |
} |
|
- (void)stop |
// See comment in header. |
{ |
self.hostName = nil; |
self.hostAddress = nil; |
self.port = 0; |
[self stopHostResolution]; |
if (self->_cfSocket != NULL) { |
CFSocketInvalidate(self->_cfSocket); |
CFRelease(self->_cfSocket); |
self->_cfSocket = NULL; |
} |
} |
|
- (void)noop |
{ |
} |
|
- (void)stopWithError:(NSError *)error |
// Stops the object, reporting the supplied error to the delegate. |
{ |
assert(error != nil); |
[self stop]; |
if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(echo:didStopWithError:)] ) { |
// The following line ensures that we don't get deallocated until the next time around the |
// run loop. This is important if our delegate holds the last reference to us and |
// this callback causes it to release that reference. At that point our object (self) gets |
// deallocated, which causes problems if any of the routines that called us reference self. |
// We prevent this problem by performing a no-op method on ourself, which keeps self alive |
// until the perform occurs. |
[self performSelector:@selector(noop) withObject:nil afterDelay:0.0]; |
[self.delegate echo:self didStopWithError:error]; |
} |
} |
|
- (void)stopWithStreamError:(CFStreamError)streamError |
// Stops the object, reporting the supplied error to the delegate. |
{ |
NSDictionary * userInfo; |
NSError * error; |
|
if (streamError.domain == kCFStreamErrorDomainNetDB) { |
userInfo = [NSDictionary dictionaryWithObjectsAndKeys: |
[NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey, |
nil |
]; |
} else { |
userInfo = nil; |
} |
error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo]; |
assert(error != nil); |
|
[self stopWithError:error]; |
} |
|
@end |