aboutsummaryrefslogtreecommitdiff
path: root/VKPC/Server.m
diff options
context:
space:
mode:
Diffstat (limited to 'VKPC/Server.m')
-rw-r--r--VKPC/Server.m455
1 files changed, 455 insertions, 0 deletions
diff --git a/VKPC/Server.m b/VKPC/Server.m
new file mode 100644
index 0000000..c00e481
--- /dev/null
+++ b/VKPC/Server.m
@@ -0,0 +1,455 @@
+//
+// Server.m
+// VKPC
+//
+// Created by Eugene on 11/29/13.
+// Copyright (c) 2013-2014 Eugene Z. All rights reserved.
+//
+
+#import "Server.h"
+#import "Controller.h"
+
+#include <libwebsockets.h>
+#include <pthread.h>
+
+#define CUSTOM_LOG 0
+
+enum {
+ LWS_LOG_ERR = 1,
+ LWS_LOG_WARN = 2,
+ LWS_LOG_NOTICE = 4,
+ LWS_LOG_INFO = 8,
+ LWS_LOG_DEBUG = 16,
+ LWS_LOG_PARSER = 32,
+ LWS_LOG_HEADER = 64,
+ LWS_LOG_EXTENSION = 128,
+ LWS_LOG_CLIENT = 256,
+ LWS_LOG_LATENCY = 512
+};
+
+#ifdef DEBUG
+static const char *lws_callback_reasons[] = {
+ "LWS_CALLBACK_ESTABLISHED",
+ "LWS_CALLBACK_CLIENT_CONNECTION_ERROR",
+ "LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH",
+ "LWS_CALLBACK_CLIENT_ESTABLISHED",
+ "LWS_CALLBACK_CLOSED",
+ "LWS_CALLBACK_CLOSED_HTTP",
+ "LWS_CALLBACK_RECEIVE",
+ "LWS_CALLBACK_CLIENT_RECEIVE",
+ "LWS_CALLBACK_CLIENT_RECEIVE_PONG",
+ "LWS_CALLBACK_CLIENT_WRITEABLE",
+ "LWS_CALLBACK_SERVER_WRITEABLE",
+ "LWS_CALLBACK_HTTP",
+ "LWS_CALLBACK_HTTP_BODY",
+ "LWS_CALLBACK_HTTP_BODY_COMPLETION",
+ "LWS_CALLBACK_HTTP_FILE_COMPLETION",
+ "LWS_CALLBACK_HTTP_WRITEABLE",
+ "LWS_CALLBACK_FILTER_NETWORK_CONNECTION",
+ "LWS_CALLBACK_FILTER_HTTP_CONNECTION",
+ "LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED",
+ "LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION",
+ "LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS",
+ "LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS",
+ "LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION",
+ "LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER",
+ "LWS_CALLBACK_CONFIRM_EXTENSION_OKAY",
+ "LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED",
+ "LWS_CALLBACK_PROTOCOL_INIT",
+ "LWS_CALLBACK_PROTOCOL_DESTROY",
+ "LWS_CALLBACK_WSI_CREATE",
+ "LWS_CALLBACK_WSI_DESTROY",
+ "LWS_CALLBACK_GET_THREAD_ID",
+ "LWS_CALLBACK_ADD_POLL_FD",
+ "LWS_CALLBACK_DEL_POLL_FD",
+ "LWS_CALLBACK_CHANGE_MODE_POLL_FD",
+ "LWS_CALLBACK_LOCK_POLL",
+ "LWS_CALLBACK_UNLOCK_POLL",
+};
+#endif
+
+static BOOL started = NO;
+static NSMutableArray *sessions;
+static NSMutableDictionary *connected;
+static struct libwebsocket_context *context;
+
+static NSThread *thread;
+static pthread_mutex_t mutex;
+static struct {
+ char *command;
+ NSInteger browser;
+} nextCommandToSend;
+
+static void ServerSession_Init(struct libwebsocket *wsi, ServerSession *s);
+static void ServerSession_CreateString(ServerSession *session);
+static void ServerSession_AppendString(ServerSession *session, const char *in);
+static void ServerSession_DestroyString(ServerSession *session);
+static void ServerSession_RecreateString(ServerSession *session);
+static void AddSession(ServerSession *session);
+static void DeleteSession(ServerSession *session);
+static void incrConnected(NSInteger browser);
+static void decrConnected(NSInteger browser);
+static int SignalingCallback(struct libwebsocket_context *this,
+ struct libwebsocket *wsi,
+ enum libwebsocket_callback_reasons reason,
+ void *user,
+ void *in,
+ size_t len);
+static void SendCommand(const char *command, NSInteger browser);
+static void ServerStart();
+
+//
+// ServerSession
+//
+static void ServerSession_Init(struct libwebsocket *wsi, ServerSession *s) {
+ s->wsi = wsi;
+ s->browser = 0;
+ s->commandToSend = NULL;
+ s->commandToSendLength = 0;
+}
+
+static void ServerSession_CreateString(ServerSession *session) {
+ session->buffer = NULL;
+ session->receivedLength = 0;
+}
+
+static void ServerSession_AppendString(ServerSession *session, const char *in) {
+ unsigned long incLength = strlen(in);
+ unsigned long newLength = session->receivedLength + incLength;
+
+ if (session->buffer == NULL) {
+ session->buffer = (char *)malloc(newLength + 1);
+ session->buffer[0] = '\0';
+ } else {
+ session->buffer = realloc(session->buffer, newLength + 1);
+ }
+
+ strcat(session->buffer, in);
+ session->receivedLength += incLength;
+}
+
+static void ServerSession_DestroyString(ServerSession *session) {
+ if (session->buffer != NULL)
+ free(session->buffer);
+ session->receivedLength = 0;
+}
+
+static void ServerSession_RecreateString(ServerSession *session) {
+ ServerSession_DestroyString(session);
+ ServerSession_CreateString(session);
+}
+
+//
+// Sessions
+//
+static void AddSession(ServerSession *session) {
+ [sessions addObject:[NSValue valueWithBytes:&session objCType:@encode(ServerSession*)]];
+// NSLog(@"[Server] AddSession, wsi points to: %p", session->wsi);
+}
+
+static void DeleteSession(ServerSession *session) {
+ for (int i = 0; i < sessions.count; i++) {
+ ServerSession *s = (ServerSession *)[(NSValue *)sessions[i] pointerValue];
+ if (s != NULL && s == session) {
+#ifdef DEBUG
+// NSLog(@"[DeleteSession] found, i=%d\n", i);
+#endif
+ [sessions removeObjectAtIndex:i];
+ decrConnected(s->browser);
+ break;
+ }
+ }
+}
+
+static void incrConnected(NSInteger browser) {
+ NSNumber *key = [NSNumber numberWithInteger:browser];
+ if (connected[key] == nil) {
+ connected[key] = @1;
+ } else {
+ NSNumber *count = (NSNumber *)connected[key];
+ connected[key] = [NSNumber numberWithInteger:[count integerValue]+1];
+ }
+}
+
+static void decrConnected(NSInteger browser) {
+ NSNumber *key = [NSNumber numberWithInteger:browser];
+ if (connected[key] != nil) {
+ NSNumber *count = (NSNumber *)connected[key];
+ if ([count integerValue] > 0) {
+ connected[key] = [NSNumber numberWithInteger:[count integerValue]-1];
+ }
+ }
+}
+
+// server callbacks
+static int SignalingCallback(struct libwebsocket_context *this,
+ struct libwebsocket *wsi,
+ enum libwebsocket_callback_reasons reason,
+ void *user,
+ void *in,
+ size_t len) {
+#ifdef DEBUG
+ if (reason != LWS_CALLBACK_GET_THREAD_ID) {
+ printf("[SignalingCallback] >>> %s\n", lws_callback_reasons[reason]);
+ }
+#endif
+
+ ServerSession *session = (ServerSession *)user;
+ switch (reason) {
+ case LWS_CALLBACK_ESTABLISHED: {
+#ifdef DEBUG
+// lwsl_info("Connection established");
+#endif
+
+// session = ServerSession_Create(wsi);
+ ServerSession_Init(wsi, session);
+ ServerSession_CreateString(session);
+ AddSession(session);
+
+ libwebsocket_callback_on_writable(context, wsi);
+ break;
+ }
+
+ case LWS_CALLBACK_SERVER_WRITEABLE: {
+ if (session->commandToSend != NULL) {
+ unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + session->commandToSendLength + LWS_SEND_BUFFER_POST_PADDING];
+ unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
+ strcpy((char *)p, session->commandToSend);
+
+// NSLog(@"[Server] LWS_CALLBACK_SERVER_WRITEABLE, commandToSend=%s", session->commandToSend);
+
+ int m = libwebsocket_write(wsi, p, session->commandToSendLength, LWS_WRITE_TEXT);
+ if (m < session->commandToSendLength) {
+ lwsl_err("ERROR while writing %d bytes to socket\n", session->commandToSendLength);
+ return -1;
+ }
+
+// NSLog(@"before free() in writable callback");
+ free(session->commandToSend);
+// NSLog(@"after free() in writable callback");
+ session->commandToSend = NULL;
+ session->commandToSendLength = 0;
+ }
+ break;
+ }
+
+ case LWS_CALLBACK_RECEIVE: {
+#ifdef DEBUG
+// printf("[lws_callback_receive] \n");
+// printf("... received string: %s\n", (const char *)in);
+// printf("... of length: %lu\n", strlen((const char *)in));
+// printf("... remaning packet: %lu\n", libwebsockets_remaining_packet_payload(wsi));
+#endif
+
+ ServerSession_AppendString(session, (const char *)in);
+#ifdef DEBUG
+// printf("... after strcat: length: %lu\n", strlen(session->buffer));
+#endif
+ if (libwebsockets_remaining_packet_payload(wsi) == 0) {
+ if (session->receivedLength == 0) {
+ //
+ } else if (session->buffer != NULL && strcmp(session->buffer, "PING") == 0) {
+ ServerSession_RecreateString(session);
+ } else {
+ NSString *copy = [NSString stringWithUTF8String:session->buffer];
+
+ ServerSession_RecreateString(session);
+
+ NSData *data = [copy dataUsingEncoding:NSUTF8StringEncoding];
+ if (data != nil) {
+ NSError *error;
+ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
+ if (error || !json || ![json isKindOfClass:[NSDictionary class]]) {
+ NSLog(@"[Server] Parse JSON error: %@; string was: %@", error, copy);
+ break;
+ }
+
+ NSString *command = json[@"command"];
+ if (command && ![command isEqual:[NSNull null]] && [command isEqualToString:@"setBrowser"]) {
+ NSInteger browserID = [(NSNumber *)json[@"_browser"] integerValue];
+ session->browser = browserID;
+
+ incrConnected(browserID);
+
+ if (![Controller isASBrowser:browserID]) {
+ [Server send:[Controller JSONForCommand:@"set_sid" data:[NSNumber numberWithInt:VKPCSessionID]] forBrowser:browserID];
+ }
+ } else {
+ #ifdef DEBUG
+ NSLog(@"in LWS_CALLBACK_RECEIVE: dispatch_async() now");
+ #endif
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [Controller handleClient:json];
+ });
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case LWS_CALLBACK_CLOSED: {
+#ifdef DEBUG
+// lwsl_info("Connection closed\n");
+#endif
+ DeleteSession(session);
+ ServerSession_DestroyString(session);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void SendCommand(const char *command, NSInteger browser) {
+ unsigned long cstrlen = strlen(command);
+ for (int i = 0; i < sessions.count; i++) {
+ ServerSession *s = (ServerSession *)[(NSValue *)sessions[i] pointerValue];
+ if (s != NULL && (s->browser == browser || browser == -1)) {
+ s->commandToSend = malloc(cstrlen + 1);
+ strcpy(s->commandToSend, command);
+ s->commandToSendLength = cstrlen;
+
+// NSLog(@"[Server] ready to send, wsi points to: %p", s->wsi);
+ libwebsocket_callback_on_writable(context, s->wsi);
+ }
+ }
+}
+
+#ifdef DEBUG
+#ifdef CUSTOM_LOG
+static NSMutableString *syslog = nil;
+static void emit_syslog(int level, const char *line) {
+ if (syslog == nil) {
+ syslog = [[NSMutableString alloc] init];
+ }
+
+ lwsl_emit_syslog(level, line);
+ [syslog appendString:[NSString stringWithFormat:@"[%d] %s", level, line]];
+// [syslog appendString:[NSString stringWithUTF8String:line]];
+// [syslog appendString:@"\n"];
+}
+#endif
+#endif
+
+static void ServerStart() {
+ sessions = [[NSMutableArray alloc] init];
+ connected = [[NSMutableDictionary alloc] init];
+ struct libwebsocket_protocols protocols[] = {
+ { "signaling-protocol", SignalingCallback, sizeof(ServerSession), 0 },
+ { NULL, NULL, 0, 0 }
+ };
+
+ pthread_mutex_init(&mutex, NULL);
+
+ nextCommandToSend.command = NULL;
+ nextCommandToSend.browser = 0;
+
+ struct lws_context_creation_info info;
+ memset(&info, 0, sizeof(info));
+
+#ifdef DEBUG
+#ifdef CUSTOM_LOG
+ lws_set_log_level(LWS_LOG_ERR | LWS_LOG_WARN | LWS_LOG_NOTICE | LWS_LOG_INFO | LWS_LOG_DEBUG | LWS_LOG_HEADER, emit_syslog);
+#else
+ lws_set_log_level(LWS_LOG_ERR | LWS_LOG_WARN | LWS_LOG_NOTICE | LWS_LOG_INFO | LWS_LOG_DEBUG | LWS_LOG_HEADER, NULL);
+#endif
+#else
+ lws_set_log_level(0, NULL);
+#endif
+
+ info.port = VKPCWSServerPort;
+ info.iface = VKPCWSServerHost;
+ info.protocols = protocols;
+ info.extensions = libwebsocket_get_internal_extensions();
+// info.ssl_cert_filepath = NULL;
+// info.ssl_private_key_filepath = NULL;
+ info.ssl_cert_filepath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"ssl_bundle.crt"] UTF8String];
+ info.ssl_private_key_filepath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"vkpc-local.ch1p.com.key"] UTF8String];
+ info.gid = -1;
+ info.uid = -1;
+#ifdef DEBUG
+ info.options = LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
+#else
+ info.options = 0;
+#endif
+
+ context = libwebsocket_create_context(&info);
+ if (context == NULL) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"VKPC Error"];
+#ifdef DEBUG
+#ifdef CUSTOM_LOG
+ [alert setInformativeText:[NSString stringWithFormat:@"Local server failed to start on port %d\n\n%@", VKPCWSServerPort, syslog]];
+#else
+ [alert setInformativeText:[NSString stringWithFormat:@"Local server failed to start on port %d", VKPCWSServerPort]];
+#endif
+#endif
+ [alert setAlertStyle:NSWarningAlertStyle];
+ [alert runModal];
+
+ return;
+ }
+
+ started = YES;
+
+ while (1) {
+ pthread_mutex_lock(&mutex);
+ if (nextCommandToSend.command != NULL) {
+ SendCommand(nextCommandToSend.command, nextCommandToSend.browser);
+ free(nextCommandToSend.command);
+ nextCommandToSend.command = NULL;
+ nextCommandToSend.browser = 0;
+ }
+ pthread_mutex_unlock(&mutex);
+ libwebsocket_service(context, 50);
+ }
+
+ libwebsocket_context_destroy(context);
+}
+
+@implementation Server
+
++ (void)start {
+ thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
+ [thread setName:@"server_thread"];
+ [thread start];
+}
+
++ (void)startThread {
+// NSLog(@"[Server startThread] current thread: %@", [[NSThread currentThread] name]);
+ ServerStart();
+}
+
++ (BOOL)send:(NSString *)command forBrowser:(NSInteger)browser {
+ if (!started) {
+ NSLog(@"[Server send:] server is not started yet, exising");
+ return NO;
+ }
+
+ pthread_mutex_lock(&mutex);
+
+ const char *command_cstr = [command UTF8String];
+ nextCommandToSend.command = malloc(strlen(command_cstr) + 1);
+ strcpy(nextCommandToSend.command, command_cstr);
+ nextCommandToSend.browser = browser;
+
+ pthread_mutex_unlock(&mutex);
+
+ return YES;
+}
+
++ (NSThread *)thread {
+ return thread;
+}
+
++ (NSInteger)connectedCount:(NSInteger)browser {
+ NSNumber *key = [NSNumber numberWithInteger:browser];
+ return connected[key] == nil ? 0 : [(NSNumber *)connected[key] integerValue];
+}
+
+@end \ No newline at end of file