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