* Added more documentation.
* Minor API changes.
5 // Created by Jens Alfke on 5/10/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
7 // Portions based on TCPServer class from Apple's "CocoaEcho" sample code.
9 #import "TCPListener.h"
10 #import "TCPConnection.h"
14 #import "ExceptionUtils.h"
17 #include <sys/socket.h>
18 #include <netinet/in.h>
22 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type,
23 CFDataRef address, const void *data, void *info);
25 @interface TCPListener()
26 @property BOOL bonjourPublished;
27 @property NSInteger bonjourError;
28 - (void) _updateTXTRecord;
32 @implementation TCPListener
35 - (id) initWithPort: (UInt16)port
48 LogTo(TCP,@"DEALLOC %@",self);
53 @synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6,
54 bonjourServiceType=_bonjourServiceType, bonjourServiceName=_bonjourServiceName,
55 bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
56 pickAvailablePort=_pickAvailablePort;
59 - (NSString*) description
61 return $sprintf(@"%@[port %hu]",self.class,_port);
65 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *ouError.
66 static void* getLastCFSocketError( NSError **outError ) {
68 *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
72 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
73 static CFSocketRef closeSocket( CFSocketRef socket ) {
75 CFSocketInvalidate(socket);
81 // opens a socket of a given protocol, either ipv4 or ipv6.
82 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily
83 address: (struct sockaddr*)address
84 error: (NSError**)error
86 CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
87 CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
88 kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
90 return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
93 setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
95 NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
96 if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
97 getLastCFSocketError(error);
98 return closeSocket(socket);
100 // set up the run loop source for the socket
101 CFRunLoopRef cfrl = CFRunLoopGetCurrent();
102 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
103 CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
108 - (BOOL) _failedToOpen: (NSError*)error
110 LogTo(TCP,@"%@ failed to open: %@",self,error);
111 [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
116 - (BOOL) open: (NSError**)outError
118 // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
120 struct sockaddr_in addr4;
121 memset(&addr4, 0, sizeof(addr4));
122 addr4.sin_len = sizeof(addr4);
123 addr4.sin_family = AF_INET;
124 addr4.sin_port = htons(_port);
125 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
128 _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
129 if( ! _ipv4socket ) {
130 if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
131 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
132 self.port++; // try the next port
134 if( outError ) *outError = error;
135 return [self _failedToOpen: error];
138 }while( ! _ipv4socket );
141 // now that the binding was successful, we get the port number
142 NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
143 const struct sockaddr_in *addr4 = addr.bytes;
144 self.port = ntohs(addr4->sin_port);
148 // set up the IPv6 endpoint
149 struct sockaddr_in6 addr6;
150 memset(&addr6, 0, sizeof(addr6));
151 addr6.sin6_len = sizeof(addr6);
152 addr6.sin6_family = AF_INET6;
153 addr6.sin6_port = htons(_port);
154 memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
156 _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: outError];
157 if( ! _ipv6socket ) {
158 _ipv4socket = closeSocket(_ipv4socket);
159 return [self _failedToOpen: *outError];
164 if( _bonjourServiceType && !_netService) {
165 // instantiate the NSNetService object that will advertise on our behalf.
166 _netService = [[NSNetService alloc] initWithDomain: @"local."
167 type: _bonjourServiceType
168 name: _bonjourServiceName ?:@""
171 [_netService setDelegate:self];
172 if( _bonjourTXTRecord )
173 [self _updateTXTRecord];
174 [_netService publish];
176 self.bonjourError = -1;
177 Warn(@"%@: Failed to create NSNetService",self);
181 LogTo(TCP,@"%@ is open",self);
182 [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
188 return [self open: nil];
197 [_netService release];
199 self.bonjourPublished = NO;
201 self.bonjourError = 0;
203 _ipv4socket = closeSocket(_ipv4socket);
204 _ipv6socket = closeSocket(_ipv6socket);
206 LogTo(BLIP,@"%@ is closed",self);
207 [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
214 return _ipv4socket != NULL;
219 #pragma mark ACCEPTING CONNECTIONS:
222 @synthesize connectionClass = _connectionClass;
225 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
227 IPAddress *addr = [IPAddress addressOfSocket: socket];
230 if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
231 && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
234 Assert(_connectionClass);
235 TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
240 if( _sslProperties ) {
241 conn.SSLProperties = _sslProperties;
242 [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
245 [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
250 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
252 TCPListener *server = (TCPListener *)info;
253 if (kCFSocketAcceptCallBack == type) {
254 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
257 accepted = [server acceptConnection: nativeSocketHandle];
258 }catchAndReport(@"TCPListenerAcceptCallBack");
260 close(nativeSocketHandle);
266 #pragma mark BONJOUR:
269 - (NSDictionary*) bonjourTXTRecord
271 return _bonjourTXTRecord;
274 - (void) setBonjourTXTRecord: (NSDictionary*)txt
276 if( ifSetObj(&_bonjourTXTRecord,txt) )
277 [self _updateTXTRecord];
280 - (void) _updateTXTRecord
284 if( _bonjourTXTRecord ) {
285 data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
287 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
289 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
292 [_netService setTXTRecordData: data];
297 - (void)netServiceWillPublish:(NSNetService *)sender
299 LogTo(BLIP,@"%@: Advertising %@",self,sender);
300 self.bonjourPublished = YES;
303 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
305 self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
306 LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
307 [_netService release];
311 - (void)netServiceDidStop:(NSNetService *)sender
313 LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
314 self.bonjourPublished = NO;
315 [_netService release];
325 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
327 Redistribution and use in source and binary forms, with or without modification, are permitted
328 provided that the following conditions are met:
330 * Redistributions of source code must retain the above copyright notice, this list of conditions
331 and the following disclaimer.
332 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
333 and the following disclaimer in the documentation and/or other materials provided with the
336 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
337 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
338 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
339 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
340 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
341 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
342 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
343 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.