bug fixes and improvements to new bonjour classes and tcplistener, also added a static library target
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 - (void) _openBonjour;
27 - (void) _publishBonjour;
28 - (void) _closeBonjour;
29 @property BOOL bonjourPublished;
30 @property NSInteger bonjourError;
31 - (void) _updateTXTRecord;
35 @implementation TCPListener
38 - (id) initWithPort: (UInt16)port
51 LogTo(TCP,@"DEALLOC %@",self);
56 @synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6,
57 bonjourServiceType=_bonjourServiceType,
58 bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
59 bonjourService=_netService,
60 pickAvailablePort=_pickAvailablePort;
63 - (NSString*) description
65 return $sprintf(@"%@[port %hu]",self.class,_port);
69 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
70 static void* getLastCFSocketError( NSError **outError ) {
72 *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
76 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
77 static CFSocketRef closeSocket( CFSocketRef socket ) {
79 CFSocketInvalidate(socket);
85 // opens a socket of a given protocol, either ipv4 or ipv6.
86 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily
87 address: (struct sockaddr*)address
88 error: (NSError**)error
90 CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
91 CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
92 kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
94 return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
97 setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
99 NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
100 if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
101 getLastCFSocketError(error);
102 return closeSocket(socket);
104 // set up the run loop source for the socket
105 CFRunLoopRef cfrl = CFRunLoopGetCurrent();
106 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
107 CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
112 - (BOOL) _failedToOpen: (NSError*)error
114 LogTo(TCP,@"%@ failed to open: %@",self,error);
115 [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
120 - (BOOL) open: (NSError**)outError
122 // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
124 struct sockaddr_in addr4;
125 memset(&addr4, 0, sizeof(addr4));
126 addr4.sin_len = sizeof(addr4);
127 addr4.sin_family = AF_INET;
128 addr4.sin_port = htons(_port);
129 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
132 _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
133 if( ! _ipv4socket ) {
134 if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
135 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
136 self.port += 1; // try the next port
138 if( outError ) *outError = error;
139 return [self _failedToOpen: error];
142 }while( ! _ipv4socket );
145 // now that the binding was successful, we get the port number
146 NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
147 const struct sockaddr_in *addr4 = addr.bytes;
148 self.port = ntohs(addr4->sin_port);
152 // set up the IPv6 endpoint
153 struct sockaddr_in6 addr6;
154 memset(&addr6, 0, sizeof(addr6));
155 addr6.sin6_len = sizeof(addr6);
156 addr6.sin6_family = AF_INET6;
157 addr6.sin6_port = htons(_port);
158 memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
161 _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
162 if( ! _ipv6socket ) {
163 _ipv4socket = closeSocket(_ipv4socket);
164 [self _failedToOpen: error];
165 if (outError) *outError = error;
172 LogTo(TCP,@"%@ is open",self);
173 [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
179 return [self open: nil];
186 [self _closeBonjour];
187 _ipv4socket = closeSocket(_ipv4socket);
188 _ipv6socket = closeSocket(_ipv6socket);
190 LogTo(BLIP,@"%@ is closed",self);
191 [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
198 return _ipv4socket != NULL;
203 #pragma mark ACCEPTING CONNECTIONS:
206 @synthesize connectionClass = _connectionClass;
209 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
211 IPAddress *addr = [IPAddress addressOfSocket: socket];
214 if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
215 && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
218 Assert(_connectionClass);
219 TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
224 if( _sslProperties ) {
225 conn.SSLProperties = _sslProperties;
226 [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
229 [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
234 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
236 TCPListener *server = (TCPListener *)info;
237 if (kCFSocketAcceptCallBack == type) {
238 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
241 accepted = [server acceptConnection: nativeSocketHandle];
242 }catchAndReport(@"TCPListenerAcceptCallBack");
244 close(nativeSocketHandle);
250 #pragma mark BONJOUR:
252 // subclasses can override if they want to call publishWithOptions: instead of publish
253 - (void) _publishBonjour
255 [_netService publish];
258 - (void) _openBonjour
260 if( self.isOpen && _bonjourServiceType && !_netService) {
261 // instantiate the NSNetService object that will advertise on our behalf.
262 _netService = [[NSNetService alloc] initWithDomain: @"local."
263 type: _bonjourServiceType
264 name: _bonjourServiceName ?:@""
267 [_netService setDelegate:self];
268 if( _bonjourTXTRecord )
269 [self _updateTXTRecord];
270 [self _publishBonjour];
272 self.bonjourError = -1;
273 Warn(@"%@: Failed to create NSNetService",self);
278 - (void) _closeBonjour
282 [_netService release];
284 self.bonjourPublished = NO;
286 if( self.bonjourError )
287 self.bonjourError = 0;
291 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
293 - (void) setBonjourServiceName: (NSString*)name
295 if( ! $equal(name,_bonjourServiceName) ) {
296 [self _closeBonjour];
297 setObj(&_bonjourServiceName,name);
303 - (NSDictionary*) bonjourTXTRecord
305 return _bonjourTXTRecord;
308 - (void) setBonjourTXTRecord: (NSDictionary*)txt
310 if( ifSetObj(&_bonjourTXTRecord,txt) )
311 [self _updateTXTRecord];
314 - (void) _updateTXTRecord
318 if( _bonjourTXTRecord ) {
319 data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
321 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
323 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
326 [_netService setTXTRecordData: data];
331 - (void)netServiceDidPublish:(NSNetService *)sender
333 LogTo(BLIP,@"%@: Advertising %@",self,sender);
334 self.bonjourPublished = YES;
337 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
339 self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
340 LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
341 [_netService release];
345 - (void)netServiceDidStop:(NSNetService *)sender
347 LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
348 self.bonjourPublished = NO;
349 [_netService release];
359 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
361 Redistribution and use in source and binary forms, with or without modification, are permitted
362 provided that the following conditions are met:
364 * Redistributions of source code must retain the above copyright notice, this list of conditions
365 and the following disclaimer.
366 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
367 and the following disclaimer in the documentation and/or other materials provided with the
370 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
371 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
372 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
373 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
374 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
375 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
376 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
377 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.