Add a new DebuggerConnection that separates out the socket logic from the handling...
[macgdbp.git] / Source / DebuggerConnection.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "DebuggerConnection.h"
18
19 #import <sys/socket.h>
20 #import <netinet/in.h>
21
22 #import "AppDelegate.h"
23 #import "LoggingController.h"
24
25 // DebuggerConnection (Private) ////////////////////////////////////////////////
26
27 @interface DebuggerConnection ()
28
29 @property (assign) CFSocketRef socket;
30 @property (assign) CFReadStreamRef readStream;
31 @property NSUInteger lastReadTransaction;
32 @property (retain) NSMutableString* currentPacket;
33 @property (assign) CFWriteStreamRef writeStream;
34 @property NSUInteger lastWrittenTransaction;
35 @property (retain) NSMutableArray* queuedWrites;
36
37 @end
38
39 // CFNetwork Callbacks /////////////////////////////////////////////////////////
40
41 void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
42 {
43 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
44 switch (eventType)
45 {
46 case kCFStreamEventHasBytesAvailable:
47 [connection readStreamHasData];
48 break;
49
50 case kCFStreamEventErrorOccurred:
51 {
52 CFErrorRef error = CFReadStreamCopyError(stream);
53 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
54 CFReadStreamClose(stream);
55 CFRelease(stream);
56 [connection errorEncountered:[[(NSError*)error autorelease] description]];
57 break;
58 }
59
60 case kCFStreamEventEndEncountered:
61 CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
62 CFReadStreamClose(stream);
63 CFRelease(stream);
64 [connection socketDisconnected];
65 break;
66 };
67 }
68
69 void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
70 {
71 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
72 switch (eventType)
73 {
74 case kCFStreamEventCanAcceptBytes:
75 [connection sendQueuedWrites];
76 break;
77
78 case kCFStreamEventErrorOccurred:
79 {
80 CFErrorRef error = CFWriteStreamCopyError(stream);
81 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
82 CFWriteStreamClose(stream);
83 CFRelease(stream);
84 [connection errorEncountered:[[(NSError*)error autorelease] description]];
85 break;
86 }
87
88 case kCFStreamEventEndEncountered:
89 CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
90 CFWriteStreamClose(stream);
91 CFRelease(stream);
92 [connection socketDisconnected];
93 break;
94 }
95 }
96
97 void SocketAcceptCallback(CFSocketRef socket,
98 CFSocketCallBackType callbackType,
99 CFDataRef address,
100 const void* data,
101 void* connectionRaw)
102 {
103 assert(callbackType == kCFSocketAcceptCallBack);
104 NSLog(@"SocketAcceptCallback()");
105
106 DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
107
108 CFReadStreamRef readStream;
109 CFWriteStreamRef writeStream;
110
111 // Create the streams on the socket.
112 CFStreamCreatePairWithSocket(kCFAllocatorDefault,
113 *(CFSocketNativeHandle*)data, // Socket handle.
114 &readStream, // Read stream in-pointer.
115 &writeStream); // Write stream in-pointer.
116
117 // Create struct to register callbacks for the stream.
118 CFStreamClientContext context;
119 context.version = 0;
120 context.info = connection;
121 context.retain = NULL;
122 context.release = NULL;
123 context.copyDescription = NULL;
124
125 // Set the client of the read stream.
126 CFOptionFlags readFlags =
127 kCFStreamEventOpenCompleted |
128 kCFStreamEventHasBytesAvailable |
129 kCFStreamEventErrorOccurred |
130 kCFStreamEventEndEncountered;
131 if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
132 // Schedule in run loop to do asynchronous communication with the engine.
133 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
134 else
135 return;
136
137 // Open the stream now that it's scheduled on the run loop.
138 if (!CFReadStreamOpen(readStream))
139 {
140 CFStreamError error = CFReadStreamGetError(readStream);
141 NSLog(@"error! %@", error);
142 return;
143 }
144
145 // Set the client of the write stream.
146 CFOptionFlags writeFlags =
147 kCFStreamEventOpenCompleted |
148 kCFStreamEventCanAcceptBytes |
149 kCFStreamEventErrorOccurred |
150 kCFStreamEventEndEncountered;
151 if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
152 // Schedule it in the run loop to receive error information.
153 CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
154 else
155 return;
156
157 // Open the write stream.
158 if (!CFWriteStreamOpen(writeStream))
159 {
160 CFStreamError error = CFWriteStreamGetError(writeStream);
161 NSLog(@"error! %@", error);
162 return;
163 }
164
165 connection.readStream = readStream;
166 connection.writeStream = writeStream;
167 [connection socketDidAccept];
168 }
169
170 ////////////////////////////////////////////////////////////////////////////////
171
172 @implementation DebuggerConnection
173
174 @synthesize socket = socket_;
175 @synthesize readStream = readStream_;
176 @synthesize lastReadTransaction = lastReadTransaction_;
177 @synthesize currentPacket = currentPacket_;
178 @synthesize writeStream = writeStream_;
179 @synthesize lastWrittenTransaction = lastWrittenTransaction_;
180 @synthesize queuedWrites = queuedWrites_;
181
182 - (void)dealloc
183 {
184 self.currentPacket = nil;
185 [super dealloc];
186 }
187
188 /**
189 * Called by SocketWrapper after the connection is successful. This immediately calls
190 * -[SocketWrapper receive] to clear the way for communication, though the information
191 * could be useful server information that we don't use right now.
192 */
193 - (void)socketDidAccept
194 {
195 connected = YES;
196 transactionID = 1;
197 self.queuedWrites = [NSMutableArray array];
198 writeQueueLock_ = [NSRecursiveLock new];
199 callTable_ = [NSMutableDictionary new];
200 }
201
202 /**
203 * Receives errors from the SocketWrapper and updates the display
204 */
205 - (void)errorEncountered:(NSString*)error
206 {
207 [delegate_ errorEncountered:error];
208 }
209
210 /**
211 * Creates, connects to, and schedules a CFSocket.
212 */
213 - (void)connect
214 {
215 // Pass ourselves to the callback so we don't have to use ugly globals.
216 CFSocketContext context;
217 context.version = 0;
218 context.info = self;
219 context.retain = NULL;
220 context.release = NULL;
221 context.copyDescription = NULL;
222
223 // Create the address structure.
224 struct sockaddr_in address;
225 memset(&address, 0, sizeof(address));
226 address.sin_len = sizeof(address);
227 address.sin_family = AF_INET;
228 address.sin_port = htons(port);
229 address.sin_addr.s_addr = htonl(INADDR_ANY);
230
231 // Create the socket signature.
232 CFSocketSignature signature;
233 signature.protocolFamily = PF_INET;
234 signature.socketType = SOCK_STREAM;
235 signature.protocol = IPPROTO_TCP;
236 signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
237
238 socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
239 &signature, // Socket signature.
240 kCFSocketAcceptCallBack, // Callback types.
241 SocketAcceptCallback, // Callout function pointer.
242 &context); // Context to pass to callout.
243 if (!socket_)
244 {
245 [self errorEncountered:@"Could not open socket."];
246 return;
247 }
248
249 // Allow old, yet-to-be recycled sockets to be reused.
250 BOOL yes = YES;
251 setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
252
253 // Schedule the socket on the run loop.
254 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
255 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
256 CFRelease(source);
257 }
258
259 /**
260 * Closes a socket and releases the ref.
261 */
262 - (void)close
263 {
264 // The socket goes down, so do the streams, which clean themselves up.
265 CFSocketInvalidate(socket_);
266 CFRelease(socket_);
267 self.queuedWrites = nil;
268 [writeQueueLock_ release];
269 [callTable_ release];
270 }
271
272 /**
273 * Notification that the socket disconnected.
274 */
275 - (void)socketDisconnected
276 {
277 [self close];
278 [delegate_ debuggerDisconnected];
279 }
280
281 /**
282 * Callback from the CFReadStream that there is data waiting to be read.
283 */
284 - (void)readStreamHasData
285 {
286 const NSUInteger kBufferSize = 1024;
287 UInt8 buffer[kBufferSize];
288 CFIndex bufferOffset = 0; // Starting point in |buffer| to work with.
289 CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
290 const char* charBuffer = (const char*)buffer;
291
292 // The read loop works by going through the buffer until all the bytes have
293 // been processed.
294 while (bufferOffset < bytesRead)
295 {
296 // Find the NULL separator, or the end of the string.
297 NSUInteger partLength = 0;
298 for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
299
300 // If there is not a current packet, set some state.
301 if (!self.currentPacket)
302 {
303 // Read the message header: the size. This will be |partLength| bytes.
304 packetSize_ = atoi(charBuffer + bufferOffset);
305 currentPacketIndex_ = 0;
306 self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
307 bufferOffset += partLength + 1; // Pass over the NULL byte.
308 continue; // Spin the loop to begin reading actual data.
309 }
310
311 // Substring the byte stream and append it to the packet string.
312 CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
313 buffer + bufferOffset, // Byte pointer, offset by start index.
314 partLength, // Length.
315 kCFStringEncodingUTF8,
316 true);
317 [self.currentPacket appendString:(NSString*)bufferString];
318 CFRelease(bufferString);
319
320 // Advance counters.
321 currentPacketIndex_ += partLength;
322 bufferOffset += partLength + 1;
323
324 // If this read finished the packet, handle it and reset.
325 NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
326 if (currentPacketIndex_ >= packetSize_)
327 {
328 [self handlePacket:[[currentPacket_ retain] autorelease]];
329 self.currentPacket = nil;
330 packetSize_ = 0;
331 currentPacketIndex_ = 0;
332 }
333 }
334 }
335
336 /**
337 * Performs the packet handling of a raw string XML packet. From this point on,
338 * the packets are associated with a transaction and are then dispatched.
339 */
340 - (void)handlePacket:(NSString*)packet
341 {
342 // Test if we can convert it into an NSXMLDocument.
343 NSError* error = nil;
344 NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
345
346 // Try to recover if we encountered an error.
347 if (!xmlTest)
348 {
349 // We do not want to starve the write queue, so manually parse out the
350 // transaction ID.
351 NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
352 if (location.location != NSNotFound)
353 {
354 NSUInteger start = location.location + location.length;
355 NSUInteger end = start;
356
357 NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
358
359 // Loop over the characters after the attribute name to extract the ID.
360 while (end < [currentPacket_ length])
361 {
362 unichar c = [currentPacket_ characterAtIndex:end];
363 if ([numericSet characterIsMember:c])
364 {
365 // If this character is numeric, extend the range to substring.
366 ++end;
367 }
368 else
369 {
370 if (start == end)
371 {
372 // If this character is nonnumeric and we have nothing in the
373 // range, skip this character.
374 ++start;
375 ++end;
376 }
377 else
378 {
379 // We've moved past the numeric ID so we should stop searching.
380 break;
381 }
382 }
383 }
384
385 // If we were able to extract the transaction ID, update the last read.
386 NSRange substringRange = NSMakeRange(start, end - start);
387 NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
388 if ([transactionStr length])
389 lastReadTransaction_ = [transactionStr intValue];
390 }
391
392 // Otherwise, assume +1 and hope it works.
393 ++lastReadTransaction_;
394 }
395 else
396 {
397 // See if the transaction can be parsed out.
398 NSInteger transaction = [self transactionIDFromResponse:xmlTest];
399 if (transaction < lastReadTransaction_)
400 {
401 NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
402 NSLog(@"out of date transaction %@", packet);
403 return;
404 }
405
406 if (transaction != lastWrittenTransaction_)
407 NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
408
409 lastReadTransaction_ = transaction;
410 }
411
412 // Log this receive event.
413 LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
414 LogEntry* log = [logger recordReceive:currentPacket_];
415 log.error = error;
416 log.lastWrittenTransactionID = lastWrittenTransaction_;
417 log.lastReadTransactionID = lastReadTransaction_;
418
419 // Finally, dispatch the handler for this response.
420 [self handleResponse:[xmlTest autorelease]];
421 }
422
423 /**
424 * Writes a command into the write stream. If the stream is ready for writing,
425 * we do so immediately. If not, the command is queued and will be written
426 * when the stream is ready.
427 */
428 - (void)send:(NSString*)command
429 {
430 if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
431 [self performSend:command];
432 else
433 [queuedWrites_ addObject:command];
434 [self sendQueuedWrites];
435 }
436
437 /**
438 * This performs a blocking send. This should ONLY be called when we know we
439 * have write access to the stream. We will busy wait in case we don't do a full
440 * send.
441 */
442 - (void)performSend:(NSString*)command
443 {
444 // If this is an out-of-date transaction, do not bother sending it.
445 NSInteger transaction = [self transactionIDFromCommand:command];
446 if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
447 return;
448
449 BOOL done = NO;
450
451 char* string = (char*)[command UTF8String];
452 int stringLength = strlen(string);
453
454 // Busy wait while writing. BAADD. Should background this operation.
455 while (!done)
456 {
457 if (CFWriteStreamCanAcceptBytes(writeStream_))
458 {
459 // Include the NULL byte in the string when we write.
460 int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
461 if (bytesWritten < 0)
462 {
463 NSLog(@"write error");
464 }
465 // Incomplete write.
466 else if (bytesWritten < strlen(string))
467 {
468 // Adjust the buffer and wait for another chance to write.
469 stringLength -= bytesWritten;
470 memmove(string, string + bytesWritten, stringLength);
471 }
472 else
473 {
474 done = YES;
475
476 // We need to scan the string to find the transactionID.
477 if (transaction == NSNotFound)
478 {
479 NSLog(@"sent %@ without a transaction ID", command);
480 continue;
481 }
482 lastWrittenTransaction_ = transaction;
483 }
484 }
485 }
486
487 // Log this trancation.
488 LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
489 LogEntry* log = [logger recordSend:command];
490 log.lastWrittenTransactionID = lastWrittenTransaction_;
491 log.lastReadTransactionID = lastReadTransaction_;
492 }
493
494 - (void)handleResponse:(NSXMLDocument*)response
495 {
496 // Check and see if there's an error.
497 NSArray* error = [[response rootElement] elementsForName:@"error"];
498 if ([error count] > 0)
499 {
500 NSLog(@"Xdebug error: %@", error);
501 [delegate_ errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
502 }
503
504 if ([[[response rootElement] name] isEqualToString:@"init"])
505 {
506 [self initReceived:response];
507 return;
508 }
509
510 NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
511 if (callbackStr)
512 {
513 SEL callback = NSSelectorFromString(callbackStr);
514 [self performSelector:callback withObject:response];
515 }
516
517 [self sendQueuedWrites];
518 }
519
520 #pragma mark Private
521
522 /**
523 * This will send a command to the debugger engine. It will append the
524 * transaction ID automatically. It accepts a NSString command along with a
525 * a variable number of arguments to substitute into the command, a la
526 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
527 */
528 - (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
529 {
530 // Collect varargs and format command.
531 va_list args;
532 va_start(args, format);
533 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
534 va_end(args);
535
536 NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
537 if (callback)
538 [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
539
540 [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
541
542 return callbackKey;
543 }
544
545 /**
546 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
547 * them if it's OK to do so. This will not block.
548 */
549 - (void)sendQueuedWrites
550 {
551 if (!connected)
552 return;
553
554 [writeQueueLock_ lock];
555 if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
556 {
557 NSString* command = [queuedWrites_ objectAtIndex:0];
558
559 // We don't want to block because this is called from the main thread.
560 // |-performSend:| busy waits when the stream is not ready. Bail out
561 // before we do that becuase busy waiting is BAD.
562 if (CFWriteStreamCanAcceptBytes(writeStream_))
563 {
564 [self performSend:command];
565 [queuedWrites_ removeObjectAtIndex:0];
566 }
567 }
568 [writeQueueLock_ unlock];
569 }
570
571
572 /**
573 * Returns the transaction_id from an NSXMLDocument.
574 */
575 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
576 {
577 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
578 }
579
580 /**
581 * Scans a command string for the transaction ID component. If it is not found,
582 * returns NSNotFound.
583 */
584 - (NSInteger)transactionIDFromCommand:(NSString*)command
585 {
586 NSRange occurrence = [command rangeOfString:@"-i "];
587 if (occurrence.location == NSNotFound)
588 return NSNotFound;
589 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
590 return [transaction intValue];
591 }
592
593 @end