Yuck -- [TCPConnection initToBonjourService:] was releasing the wrong object, the BonjourService, causing it to dealloc and eventually crash. Fixes #10.
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) _closeBonjour;
28 @property BOOL bonjourPublished;
29 @property NSInteger bonjourError;
30 - (void) _updateTXTRecord;
34 @implementation TCPListener
37 - (id) initWithPort: (UInt16)port
50 LogTo(TCP,@"DEALLOC %@",self);
55 @synthesize port=_port, useIPv6=_useIPv6,
56 bonjourServiceType=_bonjourServiceType, bonjourServiceOptions=_bonjourServiceOptions,
57 bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
58 bonjourService=_netService,
59 pickAvailablePort=_pickAvailablePort;
62 - (id<TCPListenerDelegate>) delegate {return _delegate;}
63 - (void) setDelegate: (id<TCPListenerDelegate>) delegate {_delegate = delegate;}
66 - (NSString*) description
68 return $sprintf(@"%@[port %hu]",self.class,_port);
72 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
73 static void* getLastCFSocketError( NSError **outError ) {
75 *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
79 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
80 static CFSocketRef closeSocket( CFSocketRef socket ) {
82 CFSocketInvalidate(socket);
88 // opens a socket of a given protocol, either ipv4 or ipv6.
89 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily
90 address: (struct sockaddr*)address
91 error: (NSError**)error
93 CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
94 CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
95 kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
97 return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
100 setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
102 NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
103 if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
104 getLastCFSocketError(error);
105 return closeSocket(socket);
107 // set up the run loop source for the socket
108 CFRunLoopRef cfrl = CFRunLoopGetCurrent();
109 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
110 CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
115 - (BOOL) _failedToOpen: (NSError*)error
117 LogTo(TCP,@"%@ failed to open: %@",self,error);
118 [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
123 - (BOOL) open: (NSError**)outError
125 // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
127 struct sockaddr_in addr4;
128 memset(&addr4, 0, sizeof(addr4));
129 addr4.sin_len = sizeof(addr4);
130 addr4.sin_family = AF_INET;
131 addr4.sin_port = htons(_port);
132 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
135 _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
136 if( ! _ipv4socket ) {
137 if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
138 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
139 self.port += 1; // try the next port
141 if( outError ) *outError = error;
142 return [self _failedToOpen: error];
145 }while( ! _ipv4socket );
148 // now that the binding was successful, we get the port number
149 NSData *addr = [NSMakeCollectable( CFSocketCopyAddress(_ipv4socket) ) autorelease];
150 const struct sockaddr_in *addr4 = addr.bytes;
151 self.port = ntohs(addr4->sin_port);
155 // set up the IPv6 endpoint
156 struct sockaddr_in6 addr6;
157 memset(&addr6, 0, sizeof(addr6));
158 addr6.sin6_len = sizeof(addr6);
159 addr6.sin6_family = AF_INET6;
160 addr6.sin6_port = htons(_port);
161 memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
164 _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
165 if( ! _ipv6socket ) {
166 _ipv4socket = closeSocket(_ipv4socket);
167 [self _failedToOpen: error];
168 if (outError) *outError = error;
175 LogTo(TCP,@"%@ is open",self);
176 [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
182 return [self open: nil];
189 [self _closeBonjour];
190 _ipv4socket = closeSocket(_ipv4socket);
191 _ipv6socket = closeSocket(_ipv6socket);
193 LogTo(BLIP,@"%@ is closed",self);
194 [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
201 return _ipv4socket != NULL;
206 #pragma mark ACCEPTING CONNECTIONS:
209 @synthesize connectionClass = _connectionClass;
212 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
214 IPAddress *addr = [IPAddress addressOfSocket: socket];
217 if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
218 && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
221 Assert(_connectionClass);
222 TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
227 if( _sslProperties ) {
228 conn.SSLProperties = _sslProperties;
229 [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
232 [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
237 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
239 TCPListener *server = (TCPListener *)info;
240 if (kCFSocketAcceptCallBack == type) {
241 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
244 accepted = [server acceptConnection: nativeSocketHandle];
245 }catchAndReport(@"TCPListenerAcceptCallBack");
247 close(nativeSocketHandle);
253 #pragma mark BONJOUR:
256 - (void) _openBonjour
258 if( self.isOpen && _bonjourServiceType && !_netService) {
259 // instantiate the NSNetService object that will advertise on our behalf.
260 _netService = [[NSNetService alloc] initWithDomain: @"local."
261 type: _bonjourServiceType
262 name: _bonjourServiceName ?:@""
265 [_netService setDelegate:self];
266 if( _bonjourTXTRecord )
267 [self _updateTXTRecord];
268 [_netService publishWithOptions: _bonjourServiceOptions];
270 self.bonjourError = -1;
271 Warn(@"%@: Failed to create NSNetService",self);
276 - (void) _closeBonjour
280 [_netService release];
282 self.bonjourPublished = NO;
284 if( self.bonjourError )
285 self.bonjourError = 0;
289 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
291 - (void) setBonjourServiceName: (NSString*)name
293 if( ! $equal(name,_bonjourServiceName) ) {
294 [self _closeBonjour];
295 setObj(&_bonjourServiceName,name);
301 - (NSDictionary*) bonjourTXTRecord
303 return _bonjourTXTRecord;
306 - (void) setBonjourTXTRecord: (NSDictionary*)txt
308 if( ifSetObj(&_bonjourTXTRecord,txt) )
309 [self _updateTXTRecord];
312 - (void) _updateTXTRecord
316 if( _bonjourTXTRecord ) {
317 data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
319 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
321 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
324 [_netService setTXTRecordData: data];
329 - (void)netServiceWillPublish:(NSNetService *)sender
331 LogTo(BLIP,@"%@: Advertising %@",self,sender);
332 self.bonjourPublished = YES;
335 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
337 self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
338 LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
339 [_netService release];
343 - (void)netServiceDidStop:(NSNetService *)sender
345 LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
346 self.bonjourPublished = NO;
347 [_netService release];
357 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
359 Redistribution and use in source and binary forms, with or without modification, are permitted
360 provided that the following conditions are met:
362 * Redistributions of source code must retain the above copyright notice, this list of conditions
363 and the following disclaimer.
364 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
365 and the following disclaimer in the documentation and/or other materials provided with the
368 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
369 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
370 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
371 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
372 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
373 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
374 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
375 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.