diff options
author | Sho Amano <samano@xevo.com> | 2018-07-27 13:08:32 -0700 |
---|---|---|
committer | Sho Amano <samano@xevo.com> | 2018-07-27 13:08:32 -0700 |
commit | 713f409d85e145e1bd3caa495371477f61edd855 (patch) | |
tree | c2c2b6c2bf12ffdf771ebe2872253c5a4154f47d | |
parent | 3e341359d802eed3193b11a2d272a6f86eb05615 (diff) | |
download | sdl_ios-713f409d85e145e1bd3caa495371477f61edd855.tar.gz |
Move TestTCPServer into dedicated files
-rw-r--r-- | SmartDeviceLink-iOS.xcodeproj/project.pbxproj | 6 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TestUtilities/TestTCPServer.h | 71 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TestUtilities/TestTCPServer.m | 370 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m | 367 |
4 files changed, 448 insertions, 366 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index 33e2e6222..4fb45088c 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -1253,6 +1253,7 @@ E9C32B9E1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = E9C32B9A1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h */; }; E9C32B9F1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = E9C32B9B1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.m */; }; EE5D1B33208EBCA900D17216 /* SDLTCPTransportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */; }; + EEA41D45210BA8CF0006CB6E /* TestTCPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = EEA41D44210BA8CF0006CB6E /* TestTCPServer.m */; }; EED5C9FE1F4D18D100F04000 /* SDLH264Packetizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EED5C9FD1F4D18D100F04000 /* SDLH264Packetizer.h */; }; EED5CA001F4D18DC00F04000 /* SDLRAWH264Packetizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EED5C9FF1F4D18DC00F04000 /* SDLRAWH264Packetizer.h */; }; EED5CA021F4D18EC00F04000 /* SDLRAWH264Packetizer.m in Sources */ = {isa = PBXBuildFile; fileRef = EED5CA011F4D18EC00F04000 /* SDLRAWH264Packetizer.m */; }; @@ -2637,6 +2638,8 @@ E9C32B9A1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "EAAccessoryManager+SDLProtocols.h"; sourceTree = "<group>"; }; E9C32B9B1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "EAAccessoryManager+SDLProtocols.m"; sourceTree = "<group>"; }; EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTCPTransportSpec.m; sourceTree = "<group>"; }; + EEA41D43210BA89B0006CB6E /* TestTCPServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TestTCPServer.h; path = TestUtilities/TestTCPServer.h; sourceTree = "<group>"; }; + EEA41D44210BA8CF0006CB6E /* TestTCPServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TestTCPServer.m; path = TestUtilities/TestTCPServer.m; sourceTree = "<group>"; }; EED5C9FD1F4D18D100F04000 /* SDLH264Packetizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLH264Packetizer.h; sourceTree = "<group>"; }; EED5C9FF1F4D18DC00F04000 /* SDLRAWH264Packetizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLRAWH264Packetizer.h; sourceTree = "<group>"; }; EED5CA011F4D18EC00F04000 /* SDLRAWH264Packetizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLRAWH264Packetizer.m; sourceTree = "<group>"; }; @@ -4235,6 +4238,8 @@ 5DB1BCE21D2455FD002FFC37 /* Connection Manager */, 5D6035D0202CD46200A429C9 /* SDLSpecUtilities.h */, 5D6035D1202CD46200A429C9 /* SDLSpecUtilities.m */, + EEA41D43210BA89B0006CB6E /* TestTCPServer.h */, + EEA41D44210BA8CF0006CB6E /* TestTCPServer.m */, ); name = "Test Utilities"; sourceTree = "<group>"; @@ -6556,6 +6561,7 @@ 162E82F01A9BDE8B00906325 /* SDLPowerModeQualificationStatusSpec.m in Sources */, 162E82CD1A9BDE8A00906325 /* SDLAudioStreamingStateSpec.m in Sources */, 1EE8C4461F3837D200FDC2CF /* SDLModuleDataSpec.m in Sources */, + EEA41D45210BA8CF0006CB6E /* TestTCPServer.m in Sources */, 162E831A1A9BDE8B00906325 /* SDLOnLockScreenStatusSpec.m in Sources */, 162E83431A9BDE8B00906325 /* SDLSyncPDataSpec.m in Sources */, 167ED9461A9BCE5D00797BE5 /* SwiftSpec.swift in Sources */, diff --git a/SmartDeviceLinkTests/TestUtilities/TestTCPServer.h b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.h new file mode 100644 index 000000000..9cbd0ec39 --- /dev/null +++ b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.h @@ -0,0 +1,71 @@ +// +// TestTCPServer.h +// SmartDeviceLink-iOS +// +// Created by Sho Amano on 2018/07/27. +// Copyright © 2018 Xevo Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** + * Delegate to receive various events from the test TCP server + */ +@protocol TestTCPServerDelegate +- (void)onClientConnected; +- (void)onClientDataReceived:(NSData *)data; +- (void)onClientShutdown; +- (void)onClientError; +@end + +@interface TestTCPServer : NSObject + +/** + * Sets up a TCP server that listens on specified host and port + * + * Note that this server cannot accept more than one connections from client(s). + * + * @param hostName Host name that the server will listen on + * @param portNumber TCP port number of the server + * @return YES when initialization is successful, NO otherwise + */ +- (BOOL)setup:(NSString *)hostName port:(NSString *)port; + +/** + * Shuts down the server, forcefully closing client connection + * + * @return YES when the server is successfully stopped, NO otherwise + */ +- (BOOL)teardown; + +/** + * Asynchronously sends data to connected client + * + * @param data Data to send + */ +- (void)send:(NSData *)data; + +/** + * Gracefully shuts down the connection between client. + * + * This method triggers shutdown(SHUT_WR) which is to notify that the server does not have any more data to send. + * + * @return YES if shutdown process is succeeded, NO if it's failed or client is not connected + */ +- (BOOL)shutdownClient; + +/** + * The delegate to receive server events + */ +@property (nullable, nonatomic, weak) id<TestTCPServerDelegate> delegate; + +/** + * Configure this flag to YES to enable SO_REUSEADDR option + */ +@property (nonatomic, assign) BOOL enableSOReuseAddr; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLinkTests/TestUtilities/TestTCPServer.m b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.m new file mode 100644 index 000000000..c7b332949 --- /dev/null +++ b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.m @@ -0,0 +1,370 @@ +// +// TestTCPServer.m +// SmartDeviceLinkTests +// +// Created by Sho Amano on 2018/07/27. +// Copyright © 2018 Xevo Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import <Nimble/Nimble.h> + +#import "TestTCPServer.h" + +#import <sys/types.h> +#import <sys/socket.h> +#import <netdb.h> +#import <sys/select.h> +#import <sys/time.h> +#import <fcntl.h> +#import <string.h> +#import <errno.h> + +#define MAX_SERVER_SOCKET_NUM (16) +#define RECV_BUF_SIZE (1024) +#define THREAD_STOP_WAIT_SEC (1.0) + +@interface TestTCPServer() { + int _serverSockets[MAX_SERVER_SOCKET_NUM]; + int _internalSockets[2]; + int _clientSocket; // supports only one client +} + +@property (nullable, nonatomic, strong) NSThread *thread; +@property (nonatomic, strong) dispatch_semaphore_t threadStoppedSemaphore; +@property (nonatomic, strong) NSMutableArray<NSMutableData*> *sendData; +@end + +@implementation TestTCPServer + +- (instancetype)init { + if (self = [super init]) { + for (unsigned int i = 0; i < MAX_SERVER_SOCKET_NUM; i++) { + _serverSockets[i] = -1; + } + _sendData = [[NSMutableArray alloc] init]; + _enableSOReuseAddr = YES; + } + return self; +} + +- (void)dealloc { + [self teardown]; +} + +- (BOOL)setup:(NSString *)hostName port:(NSString *)port { + int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, _internalSockets); + if (ret < 0) { + NSLog(@"TestTCPServer: socketpair() failed"); + return NO; + } + if (!([self configureSocket:_internalSockets[0]] && [self configureSocket:_internalSockets[1]])) { + return NO; + } + + struct addrinfo hints, *res; + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE /* server socket */ + | AI_NUMERICSERV /* 2nd arg is numeric port number */ + | AI_ALL | AI_V4MAPPED; /* return both IPv4 and IPv6 addresses */ + + ret = getaddrinfo([hostName UTF8String], [port UTF8String], &hints, &res); + if (ret != 0) { + NSLog(@"Error: TestTCPServer getaddrinfo() failed, %s", gai_strerror(ret)); + return NO; + } + + int socketNum = 0; + for (struct addrinfo *info = res; info != NULL && socketNum < (MAX_SERVER_SOCKET_NUM - 1); info = info->ai_next) { + int sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol); + if (sock < 0) { + NSLog(@"Error: TestTCPServer server socket creation failed"); + continue; + } + + if (![self configureServerSocket:sock]) { + close(sock); + continue; + } + + ret = bind(sock, info->ai_addr, info->ai_addrlen); + if (ret < 0) { + NSLog(@"Error: TestTCPServer server socket bind() failed: %s", strerror(errno)); + close(sock); + continue; + } + + ret = listen(sock, 5); + if (ret < 0) { + NSLog(@"Error: TestTCPServer server socket listen() failed: %s", strerror(errno)); + close(sock); + continue; + } + + _serverSockets[socketNum] = sock; + socketNum++; + } + freeaddrinfo(res); + + _clientSocket = -1; + + // create a thread and run + self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil]; + self.threadStoppedSemaphore = dispatch_semaphore_create(0); + NSLog(@"TestTCPServer: starting TCP server"); + [self.thread start]; + + return YES; +} + +- (BOOL)teardown { + if (self.thread == nil) { + return YES; + } + + BOOL result = YES; + + // wake up select() and let it stop + shutdown(_internalSockets[1], SHUT_WR); + + long ret = dispatch_semaphore_wait(self.threadStoppedSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(THREAD_STOP_WAIT_SEC * NSEC_PER_SEC))); + if (ret != 0) { + NSLog(@"Error: TestTCPServer thread doesn't stop"); + result = NO; + } + self.thread = nil; + + for (unsigned int i = 0; i < MAX_SERVER_SOCKET_NUM; i++) { + if (_serverSockets[i] >= 0) { + close(_serverSockets[i]); + _serverSockets[i] = -1; + } + } + if (_internalSockets[0] >= 0) { + close(_internalSockets[0]); + _internalSockets[0] = -1; + } + if (_internalSockets[1] >= 0) { + close(_internalSockets[1]); + _internalSockets[1] = -1; + } + + [self.sendData removeAllObjects]; + return result; +} + +- (void)send:(NSData *)data { + [self.sendData addObject:[data mutableCopy]]; + + // wake up select() + char buf[1] = {'a'}; + send(_internalSockets[1], buf, sizeof(buf), 0); +} + +- (BOOL)shutdownClient { + if (_clientSocket < 0) { + // client is not connected + return NO; + } + int ret = shutdown(_clientSocket, SHUT_WR); + if (ret != 0) { + NSLog(@"TestTCPServer: shutdown() for client socket failed: %s", strerror(errno)); + return NO; + } + return YES; +} + +- (void)run:(id)userInfo { + BOOL running = YES; + BOOL internalFailure = NO; + int ret; + + while (running) { + fd_set readfds; + fd_set writefds; + int maxFd = 0; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + for (unsigned int i = 0; _serverSockets[i] >= 0; i++) { + FD_SET(_serverSockets[i], &readfds); + if (_serverSockets[i] > maxFd) { + maxFd = _serverSockets[i]; + } + } + FD_SET(_internalSockets[0], &readfds); + if (_internalSockets[0] > maxFd) { + maxFd = _internalSockets[0]; + } + + if (_clientSocket >= 0) { + FD_SET(_clientSocket, &readfds); + if ([self.sendData count] > 0) { + FD_SET(_clientSocket, &writefds); + } + + if (_clientSocket > maxFd) { + maxFd = _clientSocket; + } + } + + ret = select(maxFd + 1, &readfds, &writefds, NULL, NULL); + if (ret < 0) { + NSLog(@"Error: TestTCPServer TCP server select() failed"); + internalFailure = YES; + break; + } + + // client socket check + if (_clientSocket >= 0) { + if (FD_ISSET(_clientSocket, &readfds)) { + char buf[RECV_BUF_SIZE]; + ssize_t recvLen = recv(_clientSocket, buf, sizeof(buf), 0); + if (recvLen < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // this is not an error + } else { + NSLog(@"TestTCPServer: recv() for client socket failed: %s", strerror(errno)); + [self.delegate onClientError]; + close(_clientSocket); + _clientSocket = -1; + } + } else if (recvLen == 0) { + [self.delegate onClientShutdown]; + // keep the socket open in case we have some more data to send + } else { + NSData *data = [NSData dataWithBytes:buf length:recvLen]; + [self.delegate onClientDataReceived:data]; + } + } + if (FD_ISSET(_clientSocket, &writefds)) { + NSMutableData *data = self.sendData[0]; + ssize_t sentLen = send(_clientSocket, data.bytes, data.length, 0); + if (sentLen < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // this is not an error + } else { + NSLog(@"TestTCPServer: send() for client socket failed: %s", strerror(errno)); + [self.delegate onClientError]; + close(_clientSocket); + _clientSocket = -1; + } + } else if (sentLen > 0) { + if (data.length == (NSUInteger)sentLen) { + [self.sendData removeObjectAtIndex:0]; + } else { + [data replaceBytesInRange:NSMakeRange(0, sentLen) withBytes:NULL length:0]; + } + } + } + } + + // server socket check + for (unsigned int i = 0; _serverSockets[i] >= 0; i++) { + int sock = _serverSockets[i]; + if (FD_ISSET(sock, &readfds)) { + struct sockaddr_storage addr; + socklen_t addrlen; + ret = accept(sock, (struct sockaddr *)&addr, &addrlen); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // this is not an error + continue; + } else { + NSLog(@"Error: TestTCPServer TCP server accept() failed: %s", strerror(errno)); + internalFailure = YES; + running = NO; + break; + } + } + + if (_clientSocket >= 0) { + NSLog(@"Error: TestTCPServer TCP server received more than one connections"); + } + + if (![self configureSocket:ret]) { + close(ret); + internalFailure = YES; + running = NO; + break; + }; + + _clientSocket = ret; + [self.delegate onClientConnected]; + } + } + + // internal pipe check + if (FD_ISSET(_internalSockets[0], &readfds)) { + char buf[16]; + ssize_t recvLen = recv(_internalSockets[0], buf, sizeof(buf), 0); + if (recvLen < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // this is not an error + } else { + NSLog(@"Error: TestTCPServer TCP server recv() failed for internal pipe: %s", strerror(errno)); + internalFailure = YES; + break; + } + } else if (recvLen == 0) { + NSLog(@"TestTCPServer: stopping TCP server"); + break; + } + } + } + + if (_clientSocket >= 0) { + close(_clientSocket); + _clientSocket = -1; + } + + expect(internalFailure == NO); + + dispatch_semaphore_signal(self.threadStoppedSemaphore); +} + +- (BOOL)configureSocket:(int)sock { + // make the socket non-blocking + int flags; + flags = fcntl(sock, F_GETFL, 0); + if (flags == -1) { + NSLog(@"Error: TestTCPServer fcntl (F_GETFL) failed"); + return NO; + } + int ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK); + if (ret == -1) { + NSLog(@"Error: TestTCPServer fcntl (F_SETFL) failed: %s", strerror(errno)); + return NO; + } + + // don't generate SIGPIPE signal + int val = 1; + ret = setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)); + if (ret != 0) { + NSLog(@"Error: TestTCPServer setsockopt() failed"); + return NO; + } + + return YES; +} + +- (BOOL)configureServerSocket:(int)sock { + if (![self configureSocket:sock]) { + return NO; + } + + if (self.enableSOReuseAddr) { + int val = 1; + int ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (ret != 0) { + NSLog(@"Error: TestTCPServer setsockopt() failed"); + return NO; + } + } + + return YES; +} +@end diff --git a/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m b/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m index 1e2eea71a..e9a25f834 100644 --- a/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m +++ b/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m @@ -13,374 +13,9 @@ #import "SDLTCPTransport.h" #import "SDLError.h" +#import "TestTCPServer.h" -#import <sys/types.h> -#import <sys/socket.h> -#import <netdb.h> -#import <sys/select.h> -#import <sys/time.h> -#import <fcntl.h> #import <stdio.h> -#import <string.h> -#import <errno.h> - -#define RECV_BUF_SIZE (1024) -#define MAX_SERVER_SOCKET_NUM (16) -#define THREAD_STOP_WAIT_SEC (1.0) - -@protocol TestTCPServerDelegate -- (void)onClientConnected; -- (void)onClientDataReceived:(NSData *)data; -- (void)onClientShutdown; -- (void)onClientError; -@end - -@interface TestTCPServer : NSObject { - int _serverSockets[MAX_SERVER_SOCKET_NUM]; - int _internalSockets[2]; - int _clientSocket; // supports only one client -} - -@property (nullable, nonatomic, weak) id<TestTCPServerDelegate> delegate; -@property (nullable, nonatomic, strong) NSThread *thread; -@property (nonatomic, strong) dispatch_semaphore_t threadStoppedSemaphore; -@property (nonatomic, strong) NSMutableArray<NSMutableData*> *sendData; -@property (nonatomic, assign) BOOL enableSOReuseAddr; -@end - -@implementation TestTCPServer - -- (instancetype)init { - if (self = [super init]) { - for (unsigned int i = 0; i < MAX_SERVER_SOCKET_NUM; i++) { - _serverSockets[i] = -1; - } - _sendData = [[NSMutableArray alloc] init]; - _enableSOReuseAddr = YES; - } - return self; -} - -- (void)dealloc { - [self teardown]; -} - -- (BOOL)setup:(NSString *)hostName port:(NSString *)port { - int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, _internalSockets); - if (ret < 0) { - NSLog(@"SDLTCPTransportSpec: socketpair() failed"); - return NO; - } - if (!([self configureSocket:_internalSockets[0]] && [self configureSocket:_internalSockets[1]])) { - return NO; - } - - struct addrinfo hints, *res; - hints.ai_family = PF_INET6; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_PASSIVE /* server socket */ - | AI_NUMERICSERV /* 2nd arg is numeric port number */ - | AI_ALL | AI_V4MAPPED; /* return both IPv4 and IPv6 addresses */ - - ret = getaddrinfo([hostName UTF8String], [port UTF8String], &hints, &res); - if (ret != 0) { - NSLog(@"Error: SDLTCPTransportSpec getaddrinfo() failed, %s", gai_strerror(ret)); - return NO; - } - - int socketNum = 0; - for (struct addrinfo *info = res; info != NULL && socketNum < (MAX_SERVER_SOCKET_NUM - 1); info = info->ai_next) { - int sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol); - if (sock < 0) { - NSLog(@"Error SDLTCPTransportSpec server socket creation failed"); - continue; - } - - if (![self configureServerSocket:sock]) { - close(sock); - continue; - } - - ret = bind(sock, info->ai_addr, info->ai_addrlen); - if (ret < 0) { - NSLog(@"Error SDLTCPTransportSpec server socket bind() failed: %s", strerror(errno)); - close(sock); - continue; - } - - ret = listen(sock, 5); - if (ret < 0) { - NSLog(@"Error SDLTCPTransportSpec server socket listen() failed: %s", strerror(errno)); - close(sock); - continue; - } - - _serverSockets[socketNum] = sock; - socketNum++; - } - freeaddrinfo(res); - - _clientSocket = -1; - - // create a thread and run - self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil]; - self.threadStoppedSemaphore = dispatch_semaphore_create(0); - NSLog(@"SDLTCPTransportSpec starting TCP server"); - [self.thread start]; - - return YES; -} - -- (BOOL)teardown { - if (self.thread == nil) { - return YES; - } - - BOOL result = YES; - - // wake up select() and let it stop - shutdown(_internalSockets[1], SHUT_WR); - - long ret = dispatch_semaphore_wait(self.threadStoppedSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(THREAD_STOP_WAIT_SEC * NSEC_PER_SEC))); - if (ret != 0) { - NSLog(@"Error: SDLTCPTransportSpec thread doesn't stop"); - result = NO; - } - self.thread = nil; - - for (unsigned int i = 0; i < MAX_SERVER_SOCKET_NUM; i++) { - if (_serverSockets[i] >= 0) { - close(_serverSockets[i]); - _serverSockets[i] = -1; - } - } - if (_internalSockets[0] >= 0) { - close(_internalSockets[0]); - _internalSockets[0] = -1; - } - if (_internalSockets[1] >= 0) { - close(_internalSockets[1]); - _internalSockets[1] = -1; - } - - [self.sendData removeAllObjects]; - return result; -} - -- (void)send:(NSData *)data { - [self.sendData addObject:[data mutableCopy]]; - - // wake up select() - char buf[1] = {'a'}; - send(_internalSockets[1], buf, sizeof(buf), 0); -} - -- (BOOL)shutdownClient { - if (_clientSocket < 0) { - // client is not connected - return NO; - } - int ret = shutdown(_clientSocket, SHUT_WR); - if (ret != 0) { - NSLog(@"SDLTCPTransportSpec shutdown() for client socket failed: %s", strerror(errno)); - return NO; - } - return YES; -} - -- (void)run:(id)userInfo { - BOOL running = YES; - BOOL internalFailure = NO; - int ret; - - while (running) { - fd_set readfds; - fd_set writefds; - int maxFd = 0; - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - - for (unsigned int i = 0; _serverSockets[i] >= 0; i++) { - FD_SET(_serverSockets[i], &readfds); - if (_serverSockets[i] > maxFd) { - maxFd = _serverSockets[i]; - } - } - FD_SET(_internalSockets[0], &readfds); - if (_internalSockets[0] > maxFd) { - maxFd = _internalSockets[0]; - } - - if (_clientSocket >= 0) { - FD_SET(_clientSocket, &readfds); - if ([self.sendData count] > 0) { - FD_SET(_clientSocket, &writefds); - } - - if (_clientSocket > maxFd) { - maxFd = _clientSocket; - } - } - - ret = select(maxFd + 1, &readfds, &writefds, NULL, NULL); - if (ret < 0) { - NSLog(@"Error: SDLTCPTransportSpec TCP server select() failed"); - internalFailure = YES; - break; - } - - // client socket check - if (_clientSocket >= 0) { - if (FD_ISSET(_clientSocket, &readfds)) { - char buf[RECV_BUF_SIZE]; - ssize_t recvLen = recv(_clientSocket, buf, sizeof(buf), 0); - if (recvLen < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // this is not an error - } else { - NSLog(@"SDLTCPTransportSpec recv() for client socket failed: %s", strerror(errno)); - [self.delegate onClientError]; - close(_clientSocket); - _clientSocket = -1; - } - } else if (recvLen == 0) { - [self.delegate onClientShutdown]; - // keep the socket open in case we have some more data to send - } else { - NSData *data = [NSData dataWithBytes:buf length:recvLen]; - [self.delegate onClientDataReceived:data]; - } - } - if (FD_ISSET(_clientSocket, &writefds)) { - NSMutableData *data = self.sendData[0]; - ssize_t sentLen = send(_clientSocket, data.bytes, data.length, 0); - if (sentLen < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // this is not an error - } else { - NSLog(@"SDLTCPTransportSpec send() for client socket failed: %s", strerror(errno)); - [self.delegate onClientError]; - close(_clientSocket); - _clientSocket = -1; - } - } else if (sentLen > 0) { - if (data.length == (NSUInteger)sentLen) { - [self.sendData removeObjectAtIndex:0]; - } else { - [data replaceBytesInRange:NSMakeRange(0, sentLen) withBytes:NULL length:0]; - } - } - } - } - - // server socket check - for (unsigned int i = 0; _serverSockets[i] >= 0; i++) { - int sock = _serverSockets[i]; - if (FD_ISSET(sock, &readfds)) { - struct sockaddr_storage addr; - socklen_t addrlen; - ret = accept(sock, (struct sockaddr *)&addr, &addrlen); - if (ret < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // this is not an error - continue; - } else { - NSLog(@"Error: SDLTCPTransportSpec TCP server accept() failed: %s", strerror(errno)); - internalFailure = YES; - running = NO; - break; - } - } - - if (_clientSocket >= 0) { - NSLog(@"Error: SDLTCPTransportSpec TCP server received more than one connections"); - } - - if (![self configureSocket:ret]) { - close(ret); - internalFailure = YES; - running = NO; - break; - }; - - _clientSocket = ret; - [self.delegate onClientConnected]; - } - } - - // internal pipe check - if (FD_ISSET(_internalSockets[0], &readfds)) { - char buf[16]; - ssize_t recvLen = recv(_internalSockets[0], buf, sizeof(buf), 0); - if (recvLen < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // this is not an error - } else { - NSLog(@"Error: SDLTCPTransportSpec TCP server recv() failed for internal pipe: %s", strerror(errno)); - internalFailure = YES; - break; - } - } else if (recvLen == 0) { - NSLog(@"SDLTCPTransportSpec stopping TCP server"); - break; - } - } - } - - if (_clientSocket >= 0) { - close(_clientSocket); - _clientSocket = -1; - } - - expect(internalFailure == NO); - - dispatch_semaphore_signal(self.threadStoppedSemaphore); -} - -- (BOOL)configureSocket:(int)sock { - // make the socket non-blocking - int flags; - flags = fcntl(sock, F_GETFL, 0); - if (flags == -1) { - NSLog(@"Error: SDLTCPTransportSpec fcntl (F_GETFL) failed"); - return NO; - } - int ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK); - if (ret == -1) { - NSLog(@"Error: SDLTCPTransportSpec fcntl (F_SETFL) failed: %s", strerror(errno)); - return NO; - } - - // don't generate SIGPIPE signal - int val = 1; - ret = setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)); - if (ret != 0) { - NSLog(@"Error: SDLTCPTransportSpec setsockopt() failed"); - return NO; - } - - return YES; -} - -- (BOOL)configureServerSocket:(int)sock { - if (![self configureSocket:sock]) { - return NO; - } - - if (self.enableSOReuseAddr) { - int val = 1; - int ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); - if (ret != 0) { - NSLog(@"Error: SDLTCPTransportSpec setsockopt() failed"); - return NO; - } - } - - return YES; -} -@end @interface SDLTCPTransport () // verify some internal properties |