summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Amano <samano@xevo.com>2018-07-27 13:08:32 -0700
committerSho Amano <samano@xevo.com>2018-07-27 13:08:32 -0700
commit713f409d85e145e1bd3caa495371477f61edd855 (patch)
treec2c2b6c2bf12ffdf771ebe2872253c5a4154f47d
parent3e341359d802eed3193b11a2d272a6f86eb05615 (diff)
downloadsdl_ios-713f409d85e145e1bd3caa495371477f61edd855.tar.gz
Move TestTCPServer into dedicated files
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj6
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestTCPServer.h71
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestTCPServer.m370
-rw-r--r--SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m367
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