// // SoulSeek.m // slskConsole // // Created by Marcelo Alves on 2007.10.16. // Copyright 2007 __MyCompanyName__. All rights reserved. // #import "SoulSeek.h" #import "TCPServer.h" int messageCount; @implementation PierceFirewallInformation -(id) init { if (!(self = [super init])) return nil; return self; } -(void) dealloc { if (user) [user release]; if (type) [type release]; [super dealloc]; } @end @implementation SoulSeek #pragma mark SoulSeek protocol /* A note about the macros : I'm using some macros to protect the code from calls in wrong threads. They are defined as follow : when_foreground / when_background => will use the block when the method is called in the foreground/background thread. It does nothing if isn't running in the correct thread. in_background_only / in_foreground_only => will execute the following block if is called in the correct thread, it shows an NSLog when the wrong thread is utilized. */ -(void) startListening { in_background_only { int port = [[self valueForPreference:@"client_listen_port"] intValue]; tcpServer = [[TCPServer alloc] init]; NSError *error = nil; [tcpServer setPort: port]; [tcpServer setDelegate: self]; if (![tcpServer start:&error] ) { NSLog(@"Error starting server: %@", error); } else { NSLog(@"Starting server on port %d", [tcpServer port]); } } } -(void) connect { when_foreground { background_call(connect); } when_background { [self startListening]; [self connectToServer]; } } - (void)TCPServer:(TCPServer *)server didReceiveConnectionFromAddress:(NSData *)addr inputStream:(NSInputStream *)inStream outputStream:(NSOutputStream *)outStream { in_background_only { SoulSeekNetworkConnection *incoming = [SoulSeekNetworkConnection newConnectionTo:slskPeer withInputStream:inStream andOutputStream:outStream andParent:self]; [incoming setCurrentPhase:ssUnknown]; // we need the 1st message to know what to do. [incomingConnections addObject:incoming]; [inStream setDelegate:incoming]; [outStream setDelegate:incoming]; [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // open the streams [inStream open]; [outStream open]; } } - (oneway void) connectToServer { in_background_only{ NSString *server; int port; connected = NO; server = [self valueForPreference:@"server_url"]; port = [[self valueForPreference:@"server_port"] intValue]; NSLog(@"Connecting to %@:%d", server, port); NSInputStream *inStream = nil; NSOutputStream *outStream = nil; NSHost* host = [NSHost hostWithName:server]; if (host == nil) { NSLog(@"Could not connect to %@", server); return; // oh, can't connect } // open the connection [NSStream getStreamsToHost:host port:port inputStream:&inStream outputStream:&outStream]; if (client != nil) [client release]; client = [SoulSeekNetworkConnection newConnectionTo:slskServer withInputStream:inStream andOutputStream:outStream andParent:self]; [inStream setDelegate:client]; [outStream setDelegate:client]; [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // open the streams [inStream open]; [outStream open]; connected = YES; NSLog(@"Connected..."); } } -(void) handleMessage:(SoulSeekMessage *)message from:(SoulSeekNetworkConnection*) connection{ in_background_only { int code = [[message valueForKey:@"type"] intValue]; if (![connection isPeer]) { [message setPos:sizeof(uint32_t)];// after the code. NSString *selectorName = [NSString stringWithFormat:@"handleServerCode%dMessage:", code]; SEL selector = NSSelectorFromString(selectorName); if ([self respondsToSelector:selector]) [self performSelector:selector withObject:message]; else NSLog(@"Skipping server message %d...", code); } else { int phase = [connection currentPhase]; if (phase == ssUnknown) { [self readPeerInitMessage:message from:connection]; return; } NSString *selectorName = [NSString stringWithFormat:@"handlePeerCode%dMessage:from:", code]; SEL selector = NSSelectorFromString(selectorName); if ([self respondsToSelector:selector]) [self performSelector:selector withObject:message withObject:connection]; else NSLog(@"Skipping peer message %d...", code); } } } -(void) login { when_foreground { background_call(login); } when_background { NSString *user; NSString *password; user = [self valueForPreference:@"user"]; password = [self valueForPreference:@"password"]; // to send the message, we must be connected to the server, and user/password must be filled. if (!connected) [self connectToServer]; if (!connected) return; if (user == nil || password == nil) return; // if ([user compare:@""] == 0 || [password compare:@""] == 0) return; // clearing the previous try loginSuccessful = NO; loginFailed = NO; if (serverMessage) { [serverMessage release]; serverMessage = nil; } if (ipAddress) { [ipAddress release]; ipAddress = nil; } // sending the message SoulSeekMessage *message = [SoulSeekMessage messageWithCode:1 order:@"ssisi" params:[NSArray arrayWithObjects: user, password, [NSNumber numberWithInt:181], [self MD5:[NSString stringWithFormat:@"%@%@", user, password]], [NSNumber numberWithInt:1], nil] ]; [client send: message]; } } -(void) roomList { NSLog(@"refreshing roomlist"); when_foreground { [[background proxyForTarget:self] roomList]; } when_background { SoulSeekMessage *message = [[SoulSeekMessage alloc] initWithData:nil andMessageType:64]; [client send: [message autorelease]]; } } -(void) getPeerAddress:(NSString *)name { when_foreground { background_call(getPeerAddress:name); } when_background { SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:3 order:@"s" params:[NSArray arrayWithObject:name]]; [client send:msg]; } } -(void) joinRoom:(NSString *)name { when_foreground { background_call(joinRoom:name); } when_background { SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:14 order:@"s" params:[NSArray arrayWithObject:name]]; [client send:msg]; } } -(void) leaveRoom:(NSString *)name { when_foreground { background_call(leaveRoom:name); } when_background { SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:15 order:@"s" params:[NSArray arrayWithObject:name]]; [client send:msg]; } } -(void) addUser:(NSString *)name { when_foreground { background_call(addUser:name); } when_background { SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:5 order:@"s" params:[NSArray arrayWithObject:name]]; [client send:msg]; } } -(void) setStatus:(int)status { when_foreground { background_call(setStatus:status); } when_background { SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:28 order:@"i" params:[NSArray arrayWithObject:[NSNumber numberWithInt:status]]]; [client send:msg]; } } -(void) sendMessage:(NSString *)message toUser:(NSString *)user { when_foreground { background_call(sendMessage:message toUser:user); } when_background { SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:22 order:@"ss" params:[NSArray arrayWithObjects:user, message, nil]]; [client send:msg]; } } -(NSArray *) rooms { NSManagedObjectContext *moc = [self managedObjectContext]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Room" inManagedObjectContext:moc]; NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; [request setEntity:entityDescription]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; [sortDescriptor release]; NSError *error = nil; NSArray *array = [moc executeFetchRequest:request error:&error]; return array; } -(void) connectTo:(NSString *)user { when_foreground { background_call(connectTo:user); } when_background { NSString *server; int port; NSManagedObject *peer = [self find:@"Peer" withName:user]; server = [peer valueForKey:@"ipAddress"]; port = [[peer valueForKey:@"port"] intValue]; NSLog(@"Connecting to peer %@ at %@:%d", user, server, port); NSInputStream *inStream = nil; NSOutputStream *outStream = nil; NSHost* host = [NSHost hostWithName:server]; if (host == nil) { NSLog(@"Could not connect to %@", server); return; // oh, can't connect } // open the connection [NSStream getStreamsToHost:host port:port inputStream:&inStream outputStream:&outStream]; SoulSeekNetworkConnection *peerConnection = [SoulSeekNetworkConnection newConnectionTo:slskPeer withInputStream:inStream andOutputStream:outStream andParent:self]; int token = ++messageCount; [peerConnection setValue:[[user copy] retain] forKey:@"relatedPeerName"]; [peerConnection setValue:[NSNumber numberWithInt:token] forKey:@"token"]; [peerConnection setCurrentPhase:ssPeerInit]; // TODO : lock the dictionary [connectedPeers setObject:peerConnection forKey:user]; [inStream setDelegate:peerConnection]; [outStream setDelegate:peerConnection]; [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // open the streams [inStream open]; [outStream open]; } } -(void) sendPeerInitTo:(NSString *)peer { SoulSeekNetworkConnection *connection = [connectedPeers objectForKey:peer]; if (!connection) { NSLog(@"Not connected to '%@'. Please use getPeerAddress and connectTo to do it first."); return; } when_foreground { background_call(sendPeerInitTo:peer); } when_background { SoulSeekMessage *message = [SoulSeekMessage message]; [message appendByte:1]; [message appendString:[self valueForPreference:@"user"]]; [message appendString:@"P"]; [message appendUInt32:[[connection valueForKey:@"token"] intValue]]; [connection send:message]; } } -(void) sendPierceFirewallTo:(NSString *)peer { when_foreground { background_call(sendPierceFirewallTo:peer); } when_background { NSNumber *token = [NSNumber numberWithInt:++messageCount]; NSString *type = @"P"; id info = [[PierceFirewallInformation alloc] init]; [info setValue:[peer retain] forKey:@"user"]; [info setValue:[type retain] forKey:@"type"]; NSLog(@"sending message... %@, %@", token, info); [pierceFirewallInfo setObject:info forKey:token]; [info autorelease]; SoulSeekMessage *msg = [SoulSeekMessage messageWithCode:18 order:@"iss" params:[NSArray arrayWithObjects:token, peer, type, nil]]; [client send:msg]; } } -(void) requestSharesFrom:(NSString *)peer { SoulSeekNetworkConnection *connection = [connectedPeers objectForKey:peer]; if (!connection) { NSLog(@"Not connected to '%@'. Please use getPeerAddress and connectTo to do it first."); return; } when_foreground { background_call(requestSharesFrom:peer); } when_background { SoulSeekMessage *message = [SoulSeekMessage message]; [message appendUInt32:4]; [connection send:message]; } } -(void) disconnect { } @end @implementation SoulSeekNetworkConnection - (id)init { self = [super init]; if (self){ outputQueue = [[NSMutableArray alloc] init]; currentPhase = ssMessageTransfer; } return self; } -(id) dealloc { [input release]; [output release]; [soulseek release]; [outputQueue removeAllObjects]; [outputQueue release]; [super dealloc]; return self; } -(void) disconnect { [input close]; [output close]; currentPhase = ssClosed; } -(void) send:(id)message { if (message == NULL) return; NSLog(@"Sending message code %@", [message valueForKey:@"type"]); [outputQueue addObject:message]; [self sendQueuedMessages]; } -(void)readMessage { // read all SoulSeek message from input if (![input hasBytesAvailable]) return; if (currentMessageData == nil) { // new message uint32_t size; // TODO : Use NSRange, please. [input read:(void *)&size maxLength:sizeof(uint32_t)]; size = CFSwapInt32LittleToHost(size); // BUG : size < 2GiB (NSData limit) if ((size > 2147483647) || (size <= 0)) { NSLog(@"Absurd packet size!"); size = 0; } currentMessageData = [[NSMutableData alloc] initWithCapacity:size]; messageSize = size; } else { // appending data to message uint8_t miniBuf; // TODO : the "max kbps" feature should be done here. [input read:&miniBuf maxLength:1]; // I don't know a way to read n bytes without blocking. [currentMessageData appendBytes:&miniBuf length:1]; } if ([currentMessageData length] >= messageSize) { SoulSeekMessage* message = [[SoulSeekMessage alloc] initWithData:currentMessageData]; [soulseek handleMessage:[message autorelease] from:self]; [currentMessageData release]; currentMessageData = nil; } if ([input hasBytesAvailable] && (currentMessageData != nil)) [self performSelector:@selector(readMessage) withObject:nil afterDelay:0.0f]; } -(void) sendQueuedMessages { if ([output hasSpaceAvailable] && ([outputQueue count] > 0)) { SoulSeekMessage *message = [[outputQueue objectAtIndex:0] retain]; [outputQueue removeObjectAtIndex:0]; // TODO : write byte to byte, to use the max kbps output. [output write:[message bytes] maxLength:[message length]]; [message autorelease]; } // being nice with runloops... if ([output hasSpaceAvailable] && ([outputQueue count] > 0)) [self performSelector:@selector(sendQueuedMessages) withObject:nil afterDelay:0.0f]; } -(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { // begin debug NSString *io; if (stream == input) io = @">>"; else io = @"<<"; NSString *event; switch (eventCode){ case NSStreamEventNone: event = @"NSStreamEventNone"; break; case NSStreamEventOpenCompleted: event = @"NSStreamEventOpenCompleted"; break; case NSStreamEventHasBytesAvailable: event = @"NSStreamEventHasBytesAvailable"; break; case NSStreamEventHasSpaceAvailable: event = @"NSStreamEventHasSpaceAvailable"; break; case NSStreamEventErrorOccurred: event = @"NSStreamEventErrorOccurred"; break; case NSStreamEventEndEncountered: event = @"NSStreamEventEndEncountered"; break; default: event = @"** Unknown"; } if ((eventCode == NSStreamEventEndEncountered) || (eventCode == NSStreamEventErrorOccurred)) { NSLog(@"Error / end of stream ocurred at %@.", ([self isPeer])?relatedPeerName:@"server"); if ([self isPeer] && currentPhase == ssPeerInit) { // failed to connect, pierce firewall now or later ? } return; } // if you want to view what is going on with the streams, uncomment the line below //NSLog(@"%@ : %@ (%@)", io, event, (isPeer)?relatedPeerName:@"server"); // end debugging if ((stream == input) && (eventCode == NSStreamEventHasBytesAvailable)) switch (currentPhase) { case ssPeerInit : case ssMessageTransfer : case ssUnknown : { [self readMessage]; break; } } if ((stream == output) && (eventCode == NSStreamEventHasSpaceAvailable)) [self sendQueuedMessages]; } - (int)currentPhase{ return currentPhase; } - (void)setCurrentPhase:(int)newCurrentPhase{ currentPhase = newCurrentPhase; } +(id) newConnectionTo: (int)type withInputStream: (NSInputStream *)inputStream andOutputStream: (NSOutputStream *)outputStream andParent: (id)parent { SoulSeekNetworkConnection* connection = [[SoulSeekNetworkConnection alloc] init]; if (connection) { [connection setValue:[inputStream retain] forKey:@"input"]; [connection setValue:[outputStream retain] forKey:@"output"]; [connection setValue:[parent retain] forKey:@"soulseek"]; [connection setValue:[NSNumber numberWithBool:(type == slskPeer)] forKey:@"isPeer"]; } return connection; } -(BOOL) isPeer { return isPeer; } @end