* Fixed: Responses still pending when a connection closed were not calling their onComplete targets.
* Fixed: BLIPTestClient target failed to build because it didn't link against zlib.
* If TCPListener.bonjourServiceName is changed while the listener is open, it now re-publishes the service with the new name.
* Added a TCPListener.bonjourService property.
* Added a BLIPMessage.representedObject property.
* Fixed a memory leak.
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 delegate=_delegate, port=_port, useIPv6=_useIPv6,
56 bonjourServiceType=_bonjourServiceType,
57 bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
58 bonjourService=_netService,
59 pickAvailablePort=_pickAvailablePort;
62 - (NSString*) description
64 return $sprintf(@"%@[port %hu]",self.class,_port);
68 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
69 static void* getLastCFSocketError( NSError **outError ) {
71 *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
75 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
76 static CFSocketRef closeSocket( CFSocketRef socket ) {
78 CFSocketInvalidate(socket);
84 // opens a socket of a given protocol, either ipv4 or ipv6.
85 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily
86 address: (struct sockaddr*)address
87 error: (NSError**)error
89 CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
90 CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
91 kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
93 return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
96 setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
98 NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
99 if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
100 getLastCFSocketError(error);
101 return closeSocket(socket);
103 // set up the run loop source for the socket
104 CFRunLoopRef cfrl = CFRunLoopGetCurrent();
105 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
106 CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
111 - (BOOL) _failedToOpen: (NSError*)error
113 LogTo(TCP,@"%@ failed to open: %@",self,error);
114 [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
119 - (BOOL) open: (NSError**)outError
121 // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
123 struct sockaddr_in addr4;
124 memset(&addr4, 0, sizeof(addr4));
125 addr4.sin_len = sizeof(addr4);
126 addr4.sin_family = AF_INET;
127 addr4.sin_port = htons(_port);
128 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
131 _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
132 if( ! _ipv4socket ) {
133 if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
134 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
135 self.port += 1; // try the next port
137 if( outError ) *outError = error;
138 return [self _failedToOpen: error];
141 }while( ! _ipv4socket );
144 // now that the binding was successful, we get the port number
145 NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
146 const struct sockaddr_in *addr4 = addr.bytes;
147 self.port = ntohs(addr4->sin_port);
151 // set up the IPv6 endpoint
152 struct sockaddr_in6 addr6;
153 memset(&addr6, 0, sizeof(addr6));
154 addr6.sin6_len = sizeof(addr6);
155 addr6.sin6_family = AF_INET6;
156 addr6.sin6_port = htons(_port);
157 memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
159 _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: outError];
160 if( ! _ipv6socket ) {
161 _ipv4socket = closeSocket(_ipv4socket);
162 return [self _failedToOpen: *outError];
168 LogTo(TCP,@"%@ is open",self);
169 [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
175 return [self open: nil];
182 [self _closeBonjour];
183 _ipv4socket = closeSocket(_ipv4socket);
184 _ipv6socket = closeSocket(_ipv6socket);
186 LogTo(BLIP,@"%@ is closed",self);
187 [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
194 return _ipv4socket != NULL;
199 #pragma mark ACCEPTING CONNECTIONS:
202 @synthesize connectionClass = _connectionClass;
205 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
207 IPAddress *addr = [IPAddress addressOfSocket: socket];
210 if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
211 && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
214 Assert(_connectionClass);
215 TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
220 if( _sslProperties ) {
221 conn.SSLProperties = _sslProperties;
222 [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
225 [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
230 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
232 TCPListener *server = (TCPListener *)info;
233 if (kCFSocketAcceptCallBack == type) {
234 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
237 accepted = [server acceptConnection: nativeSocketHandle];
238 }catchAndReport(@"TCPListenerAcceptCallBack");
240 close(nativeSocketHandle);
246 #pragma mark BONJOUR:
249 - (void) _openBonjour
251 if( self.isOpen && _bonjourServiceType && !_netService) {
252 // instantiate the NSNetService object that will advertise on our behalf.
253 _netService = [[NSNetService alloc] initWithDomain: @"local."
254 type: _bonjourServiceType
255 name: _bonjourServiceName ?:@""
258 [_netService setDelegate:self];
259 if( _bonjourTXTRecord )
260 [self _updateTXTRecord];
261 [_netService publish];
263 self.bonjourError = -1;
264 Warn(@"%@: Failed to create NSNetService",self);
269 - (void) _closeBonjour
273 [_netService release];
275 self.bonjourPublished = NO;
277 if( self.bonjourError )
278 self.bonjourError = 0;
282 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
284 - (void) setBonjourServiceName: (NSString*)name
286 if( ! $equal(name,_bonjourServiceName) ) {
287 [self _closeBonjour];
288 setObj(&_bonjourServiceName,name);
294 - (NSDictionary*) bonjourTXTRecord
296 return _bonjourTXTRecord;
299 - (void) setBonjourTXTRecord: (NSDictionary*)txt
301 if( ifSetObj(&_bonjourTXTRecord,txt) )
302 [self _updateTXTRecord];
305 - (void) _updateTXTRecord
309 if( _bonjourTXTRecord ) {
310 data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
312 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
314 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
317 [_netService setTXTRecordData: data];
322 - (void)netServiceWillPublish:(NSNetService *)sender
324 LogTo(BLIP,@"%@: Advertising %@",self,sender);
325 self.bonjourPublished = YES;
328 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
330 self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
331 LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
332 [_netService release];
336 - (void)netServiceDidStop:(NSNetService *)sender
338 LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
339 self.bonjourPublished = NO;
340 [_netService release];
350 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
352 Redistribution and use in source and binary forms, with or without modification, are permitted
353 provided that the following conditions are met:
355 * Redistributions of source code must retain the above copyright notice, this list of conditions
356 and the following disclaimer.
357 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
358 and the following disclaimer in the documentation and/or other materials provided with the
361 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
362 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
363 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
364 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
365 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
366 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
367 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
368 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.