* The BLIPConnection receivedRequest: delegate method now returns BOOL. If the method returns NO (or if the method isn't implemented in the delegate), that means it didn't handle the message at all; an error will be returned to the sender.
* If the connection closes unexpectedly due to an error, then the auto-generated responses to pending requests will contain that error. This makes it easier to display a meaningful error message in the handler for the request.
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
41 _connectionClass = [TCPConnection class];
47 - (id) initWithPort: (UInt16)port
60 LogTo(TCP,@"DEALLOC %@",self);
65 @synthesize port=_port, useIPv6=_useIPv6,
66 bonjourServiceType=_bonjourServiceType, bonjourServiceOptions=_bonjourServiceOptions,
67 bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
68 bonjourService=_netService,
69 pickAvailablePort=_pickAvailablePort;
72 - (id<TCPListenerDelegate>) delegate {return _delegate;}
73 - (void) setDelegate: (id<TCPListenerDelegate>) delegate {_delegate = delegate;}
76 - (NSString*) description
78 return $sprintf(@"%@[port %hu]",self.class,_port);
82 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
83 static void* getLastCFSocketError( NSError **outError ) {
85 *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
89 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
90 static CFSocketRef closeSocket( CFSocketRef socket ) {
92 CFSocketInvalidate(socket);
98 // opens a socket of a given protocol, either ipv4 or ipv6.
99 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily
100 address: (struct sockaddr*)address
101 error: (NSError**)error
103 CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
104 CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
105 kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
107 return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno
110 setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
112 NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
113 if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
114 getLastCFSocketError(error);
115 return closeSocket(socket);
117 // set up the run loop source for the socket
118 CFRunLoopRef cfrl = CFRunLoopGetCurrent();
119 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
120 CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
125 - (BOOL) _failedToOpen: (NSError*)error
127 LogTo(TCP,@"%@ failed to open: %@",self,error);
128 [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
133 - (BOOL) open: (NSError**)outError
135 // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
137 struct sockaddr_in addr4;
138 memset(&addr4, 0, sizeof(addr4));
139 addr4.sin_len = sizeof(addr4);
140 addr4.sin_family = AF_INET;
141 addr4.sin_port = htons(_port);
142 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
145 _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
146 if( ! _ipv4socket ) {
147 if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
148 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
149 self.port += 1; // try the next port
151 if( outError ) *outError = error;
152 return [self _failedToOpen: error];
155 }while( ! _ipv4socket );
158 // now that the binding was successful, we get the port number
159 NSData *addr = [NSMakeCollectable( CFSocketCopyAddress(_ipv4socket) ) autorelease];
160 const struct sockaddr_in *addr4 = addr.bytes;
161 self.port = ntohs(addr4->sin_port);
165 // set up the IPv6 endpoint
166 struct sockaddr_in6 addr6;
167 memset(&addr6, 0, sizeof(addr6));
168 addr6.sin6_len = sizeof(addr6);
169 addr6.sin6_family = AF_INET6;
170 addr6.sin6_port = htons(_port);
171 memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
174 _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
175 if( ! _ipv6socket ) {
176 _ipv4socket = closeSocket(_ipv4socket);
177 [self _failedToOpen: error];
178 if (outError) *outError = error;
185 LogTo(TCP,@"%@ is open",self);
186 [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
192 return [self open: nil];
199 [self _closeBonjour];
200 _ipv4socket = closeSocket(_ipv4socket);
201 _ipv6socket = closeSocket(_ipv6socket);
203 LogTo(BLIP,@"%@ is closed",self);
204 [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
211 return _ipv4socket != NULL;
216 #pragma mark ACCEPTING CONNECTIONS:
219 @synthesize connectionClass = _connectionClass;
222 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
224 IPAddress *addr = [IPAddress addressOfSocket: socket];
227 if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
228 && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
231 Assert(_connectionClass);
232 TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
237 if( _sslProperties ) {
238 conn.SSLProperties = _sslProperties;
239 [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
242 [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
247 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
249 TCPListener *server = (TCPListener *)info;
250 if (kCFSocketAcceptCallBack == type) {
251 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
254 accepted = [server acceptConnection: nativeSocketHandle];
255 }catchAndReport(@"TCPListenerAcceptCallBack");
257 close(nativeSocketHandle);
263 #pragma mark BONJOUR:
266 - (void) _openBonjour
268 if( self.isOpen && _bonjourServiceType && !_netService) {
269 // instantiate the NSNetService object that will advertise on our behalf.
270 _netService = [[NSNetService alloc] initWithDomain: @"local."
271 type: _bonjourServiceType
272 name: _bonjourServiceName ?:@""
275 [_netService setDelegate:self];
276 if( _bonjourTXTRecord )
277 [self _updateTXTRecord];
278 [_netService publishWithOptions: _bonjourServiceOptions];
280 self.bonjourError = -1;
281 Warn(@"%@: Failed to create NSNetService",self);
286 - (void) _closeBonjour
290 [_netService release];
292 self.bonjourPublished = NO;
294 if( self.bonjourError )
295 self.bonjourError = 0;
299 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
301 - (void) setBonjourServiceName: (NSString*)name
303 if( ! $equal(name,_bonjourServiceName) ) {
304 [self _closeBonjour];
305 setObj(&_bonjourServiceName,name);
311 - (NSDictionary*) bonjourTXTRecord
313 return _bonjourTXTRecord;
316 - (void) setBonjourTXTRecord: (NSDictionary*)txt
318 if( ifSetObj(&_bonjourTXTRecord,txt) )
319 [self _updateTXTRecord];
322 - (void) _updateTXTRecord
326 if( _bonjourTXTRecord ) {
327 data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
329 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
331 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
334 [_netService setTXTRecordData: data];
339 - (void)netServiceWillPublish:(NSNetService *)sender
341 LogTo(BLIP,@"%@: Advertising %@",self,sender);
342 self.bonjourPublished = YES;
345 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
347 self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
348 LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
349 [_netService release];
353 - (void)netServiceDidStop:(NSNetService *)sender
355 LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
356 self.bonjourPublished = NO;
357 [_netService release];
367 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
369 Redistribution and use in source and binary forms, with or without modification, are permitted
370 provided that the following conditions are met:
372 * Redistributions of source code must retain the above copyright notice, this list of conditions
373 and the following disclaimer.
374 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
375 and the following disclaimer in the documentation and/or other materials provided with the
378 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
379 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
380 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
381 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
382 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
383 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
384 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
385 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.