shlist

share and manage lists between multiple people
Log | Files | Refs

Network.m (8673B)


      1 #import "Network.h"
      2 #import "DataStructures.h"
      3 
      4 
      5 // #import <NSAlert.h>
      6 
      7 @interface Network () {
      8 	NSString	*device_id;
      9 	NSString	*device_id_file;
     10 	bool		 connected;
     11 }
     12 
     13 @end
     14 
     15 @implementation Network
     16 
     17 + (id) shared_network_connection
     18 {
     19 	static Network *network_connection = nil;
     20 	static dispatch_once_t onceToken;
     21 	dispatch_once(&onceToken, ^{
     22 		network_connection = [[self alloc] init];
     23 	});
     24 	return network_connection;
     25 }
     26 
     27 - (id) init
     28 {
     29 	if (self = [super init]) {
     30 		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     31 		NSString *documentsDirectory = [paths objectAtIndex:0];
     32 		device_id_file = [documentsDirectory stringByAppendingPathComponent:@"shlist_key"];
     33 		device_id = nil;
     34 
     35 		connected = 0;
     36 		[self connect];
     37 	}
     38 
     39 	return self;
     40 }
     41 
     42 - (bool) connected
     43 {
     44 	return connected;
     45 }
     46 
     47 - (NSString *) get_device_id
     48 {
     49 	return device_id;
     50 }
     51 
     52 - (void) connect
     53 {
     54 	CFReadStreamRef readStream;
     55 	CFWriteStreamRef writeStream;
     56 
     57 	CFStringRef host_name = CFSTR("absentmindedproductions.ca");
     58 	CFStreamCreatePairWithSocketToHost(NULL, host_name, 5437, &readStream, &writeStream);
     59 
     60 	input_stream = (__bridge NSInputStream *)readStream;
     61 	output_stream = (__bridge NSOutputStream *)writeStream;
     62 
     63 	[input_stream setDelegate:self];
     64 	[output_stream setDelegate:self];
     65 
     66 	[input_stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
     67 	[output_stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
     68 
     69 	// Enable SSL on both streams
     70 	[input_stream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];
     71 	[output_stream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];
     72 
     73 	[input_stream open];
     74 	[output_stream open];
     75 }
     76 
     77 - (void) disconnect
     78 {
     79 	NSLog(@"network: disconnect()");
     80 	connected = 0;
     81 	[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkDisconnectedNotification" object:nil userInfo:nil];
     82 
     83 	[input_stream close];
     84 	[output_stream close];
     85 
     86 	[input_stream removeFromRunLoop:[NSRunLoop currentRunLoop]
     87 				     forMode:NSDefaultRunLoopMode];
     88 	[output_stream removeFromRunLoop:[NSRunLoop currentRunLoop]
     89 				      forMode:NSDefaultRunLoopMode];
     90 
     91 	input_stream = nil; // stream is ivar, so reinit it
     92 	output_stream = nil; // stream is ivar, so reinit it
     93 }
     94 
     95 - (bool) load_device_id:(NSString *)phone_number;
     96 {
     97 	if ([[NSFileManager defaultManager] fileExistsAtPath:device_id_file]) {
     98 		// TODO: also check the length of the file
     99 		// read device id from filesystem into memory
    100 		NSError *error = nil;
    101 		device_id = [NSString stringWithContentsOfFile:device_id_file encoding:NSUTF8StringEncoding error:&error];
    102 		if (error != nil) {
    103 			NSLog(@"%@", [error userInfo]);
    104 			return false;
    105 		}
    106 
    107 		NSLog(@"network: device id loaded");
    108 		return true;
    109 	}
    110 
    111 	// no device id file found, send a registration message
    112 	NSMutableDictionary *request = [NSMutableDictionary dictionaryWithObjectsAndKeys:
    113 				 phone_number, @"phone_number",
    114 				 @"ios", @"os",
    115 				 nil];
    116 	[self send_message:device_add contents:request];
    117 
    118 	return false;
    119 }
    120 
    121 - (bool) send_message:(uint16_t)send_msg_type contents:(NSObject *)data
    122 {
    123 	NSMutableDictionary *request = [[NSMutableDictionary alloc] init];
    124 	[request setObject:data forKey:@"data"];
    125 
    126 	if (send_msg_type != device_add) {
    127 		// Append 'device_id' to all message types except device_add
    128 		[request setObject:device_id forKey:@"device_id"];
    129 	}
    130 
    131 	NSError *error = nil;
    132 	// Try to serialize request, bail if errors
    133 	NSData *json = [NSJSONSerialization dataWithJSONObject:request options:0 error:&error];
    134 	if (error != nil) {
    135 		NSLog(@"%@", [error userInfo]);
    136 		return false;
    137 	}
    138 
    139 	// Convert header values into network byte order
    140 	uint16_t version = htons(0);
    141 	uint16_t msg_type_network = htons(send_msg_type);
    142 	uint16_t length = htons([json length]);
    143 
    144 	// Construct message header by concatenating network byte order fields
    145 	NSMutableData *msg = [NSMutableData data];
    146 	[msg appendBytes:&version length:2];
    147 	[msg appendBytes:&msg_type_network length:2];
    148 	[msg appendBytes:&length length:2];
    149 	[msg appendData:json];
    150 
    151 	NSLog(@"network: send_message: type %i, %lu bytes",
    152 		send_msg_type, (unsigned long)[msg length]);
    153 
    154 	[output_stream write:[msg bytes] maxLength:[msg length]];
    155 
    156 	// sent successfully
    157 	return true;
    158 }
    159 
    160 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
    161 {
    162 	NSString *stream_name;
    163 	if (stream == input_stream)
    164 		stream_name = @"input stream";
    165 	else if (stream == output_stream)
    166 		stream_name = @"output stream";
    167 
    168 	switch (eventCode) {
    169 	case NSStreamEventNone: {
    170 		NSLog(@"network: NSStreamEventNone occurred");
    171 		break;
    172 	}
    173 	case NSStreamEventOpenCompleted: {
    174 		NSLog(@"network: %@ opened", stream_name);
    175 
    176 		connected = 1;
    177 		[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkConnectedNotification" object:nil userInfo:nil];
    178 		break;
    179 	}
    180 	case NSStreamEventHasBytesAvailable: {
    181 		// NSLog(@"network: %@ has bytes available", stream_name);
    182 
    183 		if (stream != input_stream) {
    184 			break;
    185 		}
    186 
    187 		// Read an entire message, header + payload
    188 		[self read_ready];
    189 
    190 		break;
    191 	}
    192 	case NSStreamEventHasSpaceAvailable: {
    193 		// NSLog(@"network: %@ has space available", stream_name);
    194 		break;
    195 	}
    196 	case NSStreamEventErrorOccurred: {
    197 		// happens when trying to connect to a down server
    198 		NSStream *error_stream;
    199 		if (stream == input_stream)
    200 			error_stream = input_stream;
    201 		else if (stream == output_stream)
    202 			error_stream = output_stream;
    203 		else
    204 			// don't try to do operations on null stream
    205 			break;
    206 
    207 		NSError *theError = [error_stream streamError];
    208 		NSLog(@"network: %@", [NSString stringWithFormat:@"%@ error %li: %@",
    209 				stream_name, (long)[theError code], [theError localizedDescription]]);
    210 
    211 		[self disconnect];
    212 
    213 		break;
    214 	}
    215 	case NSStreamEventEndEncountered: {
    216 		NSLog(@"network: %@ end encountered", stream_name);
    217 		[self disconnect];
    218 
    219 		break;
    220 	}
    221 	default:
    222 		break;
    223 	}
    224 }
    225 
    226 // Try to read and parse an entire message. If the messsage type isn't device_add,
    227 // then send a notification to the classes responsible
    228 - (void) read_ready
    229 {
    230 	// Read header
    231 	uint16_t header[3];
    232 	[self read_all:(uint8_t *)header size:6];
    233 
    234 	// Unpack header
    235 	uint16_t version = ntohs(header[0]);
    236 	uint16_t msg_type = ntohs(header[1]);
    237 	uint16_t payload_size = ntohs(header[2]);
    238 
    239 	// Verify header
    240 	if (version != 0) {
    241 		NSLog(@"read: invalid version %i", version);
    242 		return;
    243 	}
    244 	if (msg_type > 11) {
    245 		NSLog(@"read: invalid message type %i", msg_type);
    246 		return;
    247 	}
    248 
    249 	// Read payload, accept up to 64KB of data
    250 	uint8_t *payload = malloc(payload_size);
    251 	[self read_all:payload size:payload_size];
    252 	NSLog(@"read: payload is %i bytes", payload_size);
    253 
    254 	// Create new NSData wrapper around the payload bytes
    255 	NSData *data = [NSData dataWithBytesNoCopy:payload length:payload_size];
    256 
    257 	NSError *error = nil;
    258 	// Try to parse the payload as JSON, check for errors
    259 	NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data
    260 		options:0 error:&error];
    261 	if (error) {
    262 		NSLog(@"%@", [error userInfo]);
    263 		return;
    264 	}
    265 
    266 	// Make sure server sent 'status' key in response
    267 	NSString *status = response[@"status"];
    268 	if (status == nil) {
    269 		NSLog(@"read: response did not contain 'status' key");
    270 		return;
    271 	}
    272 	// Make sure 'status' key is not 'err'
    273 	if ([status compare:@"err"] == 0) {
    274 		NSLog(@"read: response error, reason = '%@'", response[@"reason"]);
    275 		return;
    276 	}
    277 
    278 	// 'data' key is always sent back when "status" is "ok"
    279 	NSObject *response_data = response[@"data"];
    280 	if (response_data == nil) {
    281 		NSLog(@"read: response did not contain 'data' key");
    282 		return;
    283 	}
    284 
    285 	if (msg_type == device_add) {
    286 		// device_add responses don't trigger any gui updates
    287 		device_id = (NSString *)response_data;
    288 
    289 		NSLog(@"device_add: writing new key '%@' to file", device_id);
    290 		NSError *error = nil;
    291 		[device_id writeToFile:device_id_file atomically:YES encoding:NSUTF8StringEncoding error:&error];
    292 
    293 		if (error != nil)
    294 			NSLog(@"%@", [error userInfo]);
    295 		return;
    296 	}
    297 
    298 	// Send out a notification that a response was received. The responsible
    299 	// parties should already be listening for these by the time they come in.
    300 	NSString *notification_name = [NSString stringWithFormat:@"NetworkResponseFor_%s", msg_strings[msg_type]];
    301 	[[NSNotificationCenter defaultCenter] postNotificationName:notification_name object:nil userInfo:response];
    302 }
    303 
    304 // Read a fixed amount of bytes
    305 - (NSInteger) read_all:(uint8_t *)data size:(unsigned int)size
    306 {
    307 	NSInteger buffer_len = [input_stream read:data maxLength:size];
    308 	if (buffer_len != size) {
    309 		NSLog(@"read_all: read %ld instead of %d bytes", (long)buffer_len, size);
    310 		return buffer_len;
    311 	}
    312 
    313 	return buffer_len;
    314 }
    315 
    316 - (void) dealloc
    317 {
    318 	[self disconnect];
    319 }
    320 
    321 @end