Add a new DebuggerConnection that separates out the socket logic from the handling...
authorRobert Sesek <rsesek@bluestatic.org>
Mon, 31 May 2010 23:44:04 +0000 (19:44 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Mon, 31 May 2010 23:44:04 +0000 (19:44 -0400)
This compiles, but will most certainly not work. This is just the initial separation of
methods and ivars that needs to be rewired.

MacGDBp.xcodeproj/project.pbxproj
Source/DebuggerConnection.h [new file with mode: 0644]
Source/DebuggerConnection.m [new file with mode: 0644]
Source/DebuggerProcessor.h
Source/DebuggerProcessor.m

index d8dcc9515d4f5262dc735de6684938f953678254..d65599af2488bc1daffb8cb432862d8cf344d845 100644 (file)
@@ -13,6 +13,7 @@
                1E02C3D50C60EC2C006F1752 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E02C3D40C60EC2C006F1752 /* AppDelegate.m */; };
                1E02C5710C610158006F1752 /* DebuggerProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E02C5700C610158006F1752 /* DebuggerProcessor.m */; };
                1E02C5F60C610724006F1752 /* DebuggerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E02C5F50C610724006F1752 /* DebuggerController.m */; };
+               1E0724E311B47BCC0017AD3C /* DebuggerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */; };
                1E0AFBB90FC2518700C67031 /* HUDIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E0AFBB80FC2518700C67031 /* HUDIcon.png */; };
                1E1E53030DF9B89800D334F9 /* Breakpoints.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E1E53010DF9B89800D334F9 /* Breakpoints.xib */; };
                1E35FC760C6579CA0030F527 /* NSXMLElementAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E35FC750C6579CA0030F527 /* NSXMLElementAdditions.m */; };
@@ -71,6 +72,8 @@
                1E02C5700C610158006F1752 /* DebuggerProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerProcessor.m; path = Source/DebuggerProcessor.m; sourceTree = "<group>"; };
                1E02C5F40C610724006F1752 /* DebuggerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DebuggerController.h; path = Source/DebuggerController.h; sourceTree = "<group>"; };
                1E02C5F50C610724006F1752 /* DebuggerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerController.m; path = Source/DebuggerController.m; sourceTree = "<group>"; };
+               1E0724E111B47BCC0017AD3C /* DebuggerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DebuggerConnection.h; path = Source/DebuggerConnection.h; sourceTree = "<group>"; };
+               1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerConnection.m; path = Source/DebuggerConnection.m; sourceTree = "<group>"; };
                1E0AFBB80FC2518700C67031 /* HUDIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = HUDIcon.png; path = Icons/HUDIcon.png; sourceTree = "<group>"; };
                1E1E53020DF9B89800D334F9 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/Breakpoints.xib; sourceTree = "<group>"; };
                1E35FC750C6579CA0030F527 /* NSXMLElementAdditions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = NSXMLElementAdditions.m; path = Source/NSXMLElementAdditions.m; sourceTree = "<group>"; };
                1E1E52C10DF9B1FB00D334F9 /* Connection */ = {
                        isa = PBXGroup;
                        children = (
+                               1E0724E111B47BCC0017AD3C /* DebuggerConnection.h */,
+                               1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */,
                                1E02C56F0C610158006F1752 /* DebuggerProcessor.h */,
                                1E02C5700C610158006F1752 /* DebuggerProcessor.m */,
                                1E35FFB00C65A74C0030F527 /* NSXMLElementAdditions.h */,
                                1EBF4D5D0EE35F0700B62769 /* StackController.m in Sources */,
                                1E67E6FD0F3C052000E68F1B /* PreferencesPathsArrayController.m in Sources */,
                                1E6B5947116106FE001189D2 /* LoggingController.m in Sources */,
+                               1E0724E311B47BCC0017AD3C /* DebuggerConnection.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
diff --git a/Source/DebuggerConnection.h b/Source/DebuggerConnection.h
new file mode 100644 (file)
index 0000000..1343d1f
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * MacGDBp
+ * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
+ * 
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU 
+ * General Public License as published by the Free Software Foundation; either version 2 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with this program; if not, 
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@class LoggingController;
+
+@interface DebuggerConnection : NSObject
+{
+       // The port to connect on.
+       NSUInteger port;
+       
+       // If the connection to the debugger engine is currently active.
+       BOOL connected;
+
+       // The raw CFSocket on which the two streams are based. Strong.
+       CFSocketRef socket_;
+       
+       // The read stream that is scheduled on the main run loop. Weak.
+       CFReadStreamRef readStream_;
+       
+       // The write stream. Weak.
+       CFWriteStreamRef writeStream_;
+       
+       // An ever-increasing integer that gives each transaction a unique ID for the
+       // debugging engine.
+       NSUInteger transactionID;
+       
+       // The most recently received transaction ID.
+       NSUInteger lastReadTransaction_;
+       
+       // The last transactionID written to the stream.
+       NSUInteger lastWrittenTransaction_;
+       
+       // Callback table. This maps transaction IDs to selectors. When the engine
+       // returns a response to the debugger, we will dispatch the response XML to
+       // the selector, based on transaction_id.
+       NSMutableDictionary* callTable_;
+       
+       // To prevent blocked writing, we enqueue all writes and then wait for the
+       // write stream to tell us it's ready. We store the pending commands in this
+       // array. We use this as a stack (FIFO), with index 0 being first.
+       NSMutableArray* queuedWrites_;
+       
+       // We send queued writes in multiple places, sometimes off a run loop event.
+       // Because of this, we need to ensure that only one client is dequeing and
+       // sending at a time.
+       NSRecursiveLock* writeQueueLock_;
+       
+       // Information about the current read loop. We append to |currentPacket_|
+       // until |currentPacketSize_| has reached |packetSize_|.
+       NSMutableString* currentPacket_;
+       int packetSize_;
+       int currentPacketIndex_;
+
+       // The delegate.
+       id delegate_;
+}
+
+- (void)connect;
+- (void)close;
+- (void)socketDidAccept;
+- (void)socketDisconnected;
+- (void)readStreamHasData;
+- (void)send:(NSString*)command;
+- (void)performSend:(NSString*)command;
+- (void)errorEncountered:(NSString*)error;
+
+- (void)handleResponse:(NSXMLDocument*)response;
+- (void)handlePacket:(NSString*)packet;
+
+- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...;
+
+- (void)sendQueuedWrites;
+
+- (NSString*)escapedURIPath:(NSString*)path;
+- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response;
+- (NSInteger)transactionIDFromCommand:(NSString*)command;
+
+@end
diff --git a/Source/DebuggerConnection.m b/Source/DebuggerConnection.m
new file mode 100644 (file)
index 0000000..190f15d
--- /dev/null
@@ -0,0 +1,593 @@
+/*
+ * MacGDBp
+ * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
+ * 
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU 
+ * General Public License as published by the Free Software Foundation; either version 2 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with this program; if not, 
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#import "DebuggerConnection.h"
+
+#import <sys/socket.h>
+#import <netinet/in.h>
+
+#import "AppDelegate.h"
+#import "LoggingController.h"
+
+// DebuggerConnection (Private) ////////////////////////////////////////////////
+
+@interface DebuggerConnection ()
+
+@property (assign) CFSocketRef socket;
+@property (assign) CFReadStreamRef readStream;
+@property NSUInteger lastReadTransaction;
+@property (retain) NSMutableString* currentPacket;
+@property (assign) CFWriteStreamRef writeStream;
+@property NSUInteger lastWrittenTransaction;
+@property (retain) NSMutableArray* queuedWrites;
+
+@end
+
+// CFNetwork Callbacks /////////////////////////////////////////////////////////
+
+void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
+{
+       DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
+       switch (eventType)
+       {
+               case kCFStreamEventHasBytesAvailable:
+                       [connection readStreamHasData];
+                       break;
+                       
+               case kCFStreamEventErrorOccurred:
+               {
+                       CFErrorRef error = CFReadStreamCopyError(stream);
+                       CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+                       CFReadStreamClose(stream);
+                       CFRelease(stream);
+                       [connection errorEncountered:[[(NSError*)error autorelease] description]];
+                       break;
+               }
+                       
+               case kCFStreamEventEndEncountered:
+                       CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+                       CFReadStreamClose(stream);
+                       CFRelease(stream);
+                       [connection socketDisconnected];
+                       break;
+       };
+}
+
+void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
+{
+       DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
+       switch (eventType)
+       {
+               case kCFStreamEventCanAcceptBytes:
+                       [connection sendQueuedWrites];
+                       break;
+                       
+               case kCFStreamEventErrorOccurred:
+               {
+                       CFErrorRef error = CFWriteStreamCopyError(stream);
+                       CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+                       CFWriteStreamClose(stream);
+                       CFRelease(stream);
+                       [connection errorEncountered:[[(NSError*)error autorelease] description]];
+                       break;
+               }
+                       
+               case kCFStreamEventEndEncountered:
+                       CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+                       CFWriteStreamClose(stream);
+                       CFRelease(stream);
+                       [connection socketDisconnected];
+                       break;
+       }
+}
+
+void SocketAcceptCallback(CFSocketRef socket,
+                                                                                                       CFSocketCallBackType callbackType,
+                                                                                                       CFDataRef address,
+                                                                                                       const void* data,
+                                                                                                       void* connectionRaw)
+{
+       assert(callbackType == kCFSocketAcceptCallBack);
+       NSLog(@"SocketAcceptCallback()");
+       
+       DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
+       
+       CFReadStreamRef readStream;
+       CFWriteStreamRef writeStream;
+       
+       // Create the streams on the socket.
+       CFStreamCreatePairWithSocket(kCFAllocatorDefault,
+                                                                                                                        *(CFSocketNativeHandle*)data,  // Socket handle.
+                                                                                                                        &readStream,  // Read stream in-pointer.
+                                                                                                                        &writeStream);  // Write stream in-pointer.
+       
+       // Create struct to register callbacks for the stream.
+       CFStreamClientContext context;
+       context.version = 0;
+       context.info = connection;
+       context.retain = NULL;
+       context.release = NULL;
+       context.copyDescription = NULL;
+       
+       // Set the client of the read stream.
+       CFOptionFlags readFlags =
+       kCFStreamEventOpenCompleted |
+       kCFStreamEventHasBytesAvailable |
+       kCFStreamEventErrorOccurred |
+       kCFStreamEventEndEncountered;
+       if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
+               // Schedule in run loop to do asynchronous communication with the engine.
+               CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+       else
+               return;
+       
+       // Open the stream now that it's scheduled on the run loop.
+       if (!CFReadStreamOpen(readStream))
+       {
+               CFStreamError error = CFReadStreamGetError(readStream);
+               NSLog(@"error! %@", error);
+               return;
+       }
+       
+       // Set the client of the write stream.
+       CFOptionFlags writeFlags =
+       kCFStreamEventOpenCompleted |
+       kCFStreamEventCanAcceptBytes |
+       kCFStreamEventErrorOccurred |
+       kCFStreamEventEndEncountered;
+       if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
+               // Schedule it in the run loop to receive error information.
+               CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+       else
+               return;
+       
+       // Open the write stream.
+       if (!CFWriteStreamOpen(writeStream))
+       {
+               CFStreamError error = CFWriteStreamGetError(writeStream);
+               NSLog(@"error! %@", error);
+               return;
+       }
+       
+       connection.readStream = readStream;
+       connection.writeStream = writeStream;
+       [connection socketDidAccept];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+@implementation DebuggerConnection
+
+@synthesize socket = socket_;
+@synthesize readStream = readStream_;
+@synthesize lastReadTransaction = lastReadTransaction_;
+@synthesize currentPacket = currentPacket_;
+@synthesize writeStream = writeStream_;
+@synthesize lastWrittenTransaction = lastWrittenTransaction_;
+@synthesize queuedWrites = queuedWrites_;
+
+- (void)dealloc
+{
+       self.currentPacket = nil;
+       [super dealloc];
+}
+
+/**
+ * Called by SocketWrapper after the connection is successful. This immediately calls
+ * -[SocketWrapper receive] to clear the way for communication, though the information
+ * could be useful server information that we don't use right now.
+ */
+- (void)socketDidAccept
+{
+       connected = YES;
+       transactionID = 1;
+       self.queuedWrites = [NSMutableArray array];
+       writeQueueLock_ = [NSRecursiveLock new];
+       callTable_ = [NSMutableDictionary new];
+}
+
+/**
+ * Receives errors from the SocketWrapper and updates the display
+ */
+- (void)errorEncountered:(NSString*)error
+{
+       [delegate_ errorEncountered:error];
+}
+
+/**
+ * Creates, connects to, and schedules a CFSocket.
+ */
+- (void)connect
+{
+       // Pass ourselves to the callback so we don't have to use ugly globals.
+       CFSocketContext context;
+       context.version = 0;
+       context.info = self;
+       context.retain = NULL;
+       context.release = NULL;
+       context.copyDescription = NULL;
+       
+       // Create the address structure.
+       struct sockaddr_in address;
+       memset(&address, 0, sizeof(address));
+       address.sin_len = sizeof(address);
+       address.sin_family = AF_INET;
+       address.sin_port = htons(port);
+       address.sin_addr.s_addr = htonl(INADDR_ANY);            
+       
+       // Create the socket signature.
+       CFSocketSignature signature;
+       signature.protocolFamily = PF_INET;
+       signature.socketType = SOCK_STREAM;
+       signature.protocol = IPPROTO_TCP;
+       signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
+       
+       socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
+                                                                                                                                                                                       &signature,  // Socket signature.
+                                                                                                                                                                                       kCFSocketAcceptCallBack,  // Callback types.
+                                                                                                                                                                                       SocketAcceptCallback,  // Callout function pointer.
+                                                                                                                                                                                       &context);  // Context to pass to callout.
+       if (!socket_)
+       {
+               [self errorEncountered:@"Could not open socket."];
+               return;
+       }
+       
+       // Allow old, yet-to-be recycled sockets to be reused.
+       BOOL yes = YES;
+       setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
+       
+       // Schedule the socket on the run loop.
+       CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
+       CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
+       CFRelease(source);
+}
+
+/**
+ * Closes a socket and releases the ref.
+ */
+- (void)close
+{
+       // The socket goes down, so do the streams, which clean themselves up.
+       CFSocketInvalidate(socket_);
+       CFRelease(socket_);
+       self.queuedWrites = nil;
+       [writeQueueLock_ release];
+       [callTable_ release];
+}
+
+/**
+ * Notification that the socket disconnected.
+ */
+- (void)socketDisconnected
+{
+       [self close];
+       [delegate_ debuggerDisconnected];
+}
+
+/**
+ * Callback from the CFReadStream that there is data waiting to be read.
+ */
+- (void)readStreamHasData
+{
+       const NSUInteger kBufferSize = 1024;
+       UInt8 buffer[kBufferSize];
+       CFIndex bufferOffset = 0;  // Starting point in |buffer| to work with.
+       CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
+       const char* charBuffer = (const char*)buffer;
+       
+       // The read loop works by going through the buffer until all the bytes have
+       // been processed.
+       while (bufferOffset < bytesRead)
+       {
+               // Find the NULL separator, or the end of the string.
+               NSUInteger partLength = 0;
+               for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
+               
+               // If there is not a current packet, set some state.
+               if (!self.currentPacket)
+               {
+                       // Read the message header: the size.  This will be |partLength| bytes.
+                       packetSize_ = atoi(charBuffer + bufferOffset);
+                       currentPacketIndex_ = 0;
+                       self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
+                       bufferOffset += partLength + 1;  // Pass over the NULL byte.
+                       continue;  // Spin the loop to begin reading actual data.
+               }
+               
+               // Substring the byte stream and append it to the packet string.
+               CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
+                                                                                                                                                                                                                        buffer + bufferOffset,  // Byte pointer, offset by start index.
+                                                                                                                                                                                                                        partLength,  // Length.
+                                                                                                                                                                                                                        kCFStringEncodingUTF8,
+                                                                                                                                                                                                                        true);
+               [self.currentPacket appendString:(NSString*)bufferString];
+               CFRelease(bufferString);
+               
+               // Advance counters.
+               currentPacketIndex_ += partLength;
+               bufferOffset += partLength + 1;
+               
+               // If this read finished the packet, handle it and reset.
+               NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
+               if (currentPacketIndex_ >= packetSize_)
+               {
+                       [self handlePacket:[[currentPacket_ retain] autorelease]];
+                       self.currentPacket = nil;
+                       packetSize_ = 0;
+                       currentPacketIndex_ = 0;
+               }
+       }
+}
+
+/**
+ * Performs the packet handling of a raw string XML packet. From this point on,
+ * the packets are associated with a transaction and are then dispatched.
+ */
+- (void)handlePacket:(NSString*)packet
+{
+       // Test if we can convert it into an NSXMLDocument.
+       NSError* error = nil;
+       NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
+       
+       // Try to recover if we encountered an error.
+       if (!xmlTest)
+       {
+               // We do not want to starve the write queue, so manually parse out the
+               // transaction ID.
+               NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
+               if (location.location != NSNotFound)
+               {
+                       NSUInteger start = location.location + location.length;
+                       NSUInteger end = start;
+                       
+                       NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
+                       
+                       // Loop over the characters after the attribute name to extract the ID.
+                       while (end < [currentPacket_ length])
+                       {
+                               unichar c = [currentPacket_ characterAtIndex:end];
+                               if ([numericSet characterIsMember:c])
+                               {
+                                       // If this character is numeric, extend the range to substring.
+                                       ++end;
+                               }
+                               else
+                               {
+                                       if (start == end)
+                                       {
+                                               // If this character is nonnumeric and we have nothing in the
+                                               // range, skip this character.
+                                               ++start;
+                                               ++end;
+                                       }
+                                       else
+                                       {
+                                               // We've moved past the numeric ID so we should stop searching.
+                                               break;
+                                       }
+                               }
+                       }
+                       
+                       // If we were able to extract the transaction ID, update the last read.
+                       NSRange substringRange = NSMakeRange(start, end - start);
+                       NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
+                       if ([transactionStr length])
+                               lastReadTransaction_ = [transactionStr intValue];
+               }
+               
+               // Otherwise, assume +1 and hope it works.
+               ++lastReadTransaction_;
+       }
+       else
+       {
+               // See if the transaction can be parsed out.
+               NSInteger transaction = [self transactionIDFromResponse:xmlTest];
+               if (transaction < lastReadTransaction_)
+               {
+                       NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
+                       NSLog(@"out of date transaction %@", packet);
+                       return;
+               }
+               
+               if (transaction != lastWrittenTransaction_)
+                       NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
+               
+               lastReadTransaction_ = transaction;
+       }
+       
+       // Log this receive event.
+       LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
+       LogEntry* log = [logger recordReceive:currentPacket_];
+       log.error = error;
+       log.lastWrittenTransactionID = lastWrittenTransaction_;
+       log.lastReadTransactionID = lastReadTransaction_;
+       
+       // Finally, dispatch the handler for this response.
+       [self handleResponse:[xmlTest autorelease]];    
+}
+
+/**
+ * Writes a command into the write stream. If the stream is ready for writing,
+ * we do so immediately. If not, the command is queued and will be written
+ * when the stream is ready.
+ */
+- (void)send:(NSString*)command
+{
+       if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
+               [self performSend:command];
+       else
+               [queuedWrites_ addObject:command];
+       [self sendQueuedWrites];
+}
+
+/**
+ * This performs a blocking send. This should ONLY be called when we know we
+ * have write access to the stream. We will busy wait in case we don't do a full
+ * send.
+ */
+- (void)performSend:(NSString*)command
+{
+       // If this is an out-of-date transaction, do not bother sending it.
+       NSInteger transaction = [self transactionIDFromCommand:command];
+       if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
+               return;
+       
+       BOOL done = NO;
+       
+       char* string = (char*)[command UTF8String];
+       int stringLength = strlen(string);
+       
+       // Busy wait while writing. BAADD. Should background this operation.
+       while (!done)
+       {
+               if (CFWriteStreamCanAcceptBytes(writeStream_))
+               {
+                       // Include the NULL byte in the string when we write.
+                       int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
+                       if (bytesWritten < 0)
+                       {
+                               NSLog(@"write error");
+                       }
+                       // Incomplete write.
+                       else if (bytesWritten < strlen(string))
+                       {
+                               // Adjust the buffer and wait for another chance to write.
+                               stringLength -= bytesWritten;
+                               memmove(string, string + bytesWritten, stringLength);
+                       }
+                       else
+                       {
+                               done = YES;
+                               
+                               // We need to scan the string to find the transactionID.
+                               if (transaction == NSNotFound)
+                               {
+                                       NSLog(@"sent %@ without a transaction ID", command);
+                                       continue;
+                               }
+                               lastWrittenTransaction_ = transaction;
+                       }
+               }
+       }
+       
+       // Log this trancation.
+       LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
+       LogEntry* log = [logger recordSend:command];
+       log.lastWrittenTransactionID = lastWrittenTransaction_;
+       log.lastReadTransactionID = lastReadTransaction_;
+}
+
+- (void)handleResponse:(NSXMLDocument*)response
+{
+       // Check and see if there's an error.
+       NSArray* error = [[response rootElement] elementsForName:@"error"];
+       if ([error count] > 0)
+       {
+               NSLog(@"Xdebug error: %@", error);
+               [delegate_ errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
+       }
+       
+       if ([[[response rootElement] name] isEqualToString:@"init"])
+       {
+               [self initReceived:response];
+               return;
+       }
+       
+       NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
+       if (callbackStr)
+       {
+               SEL callback = NSSelectorFromString(callbackStr);
+               [self performSelector:callback withObject:response];
+       }
+       
+       [self sendQueuedWrites];
+}
+
+#pragma mark Private
+
+/**
+ * This will send a command to the debugger engine. It will append the
+ * transaction ID automatically. It accepts a NSString command along with a
+ * a variable number of arguments to substitute into the command, a la
+ * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
+ */
+- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
+{
+       // Collect varargs and format command.
+       va_list args;
+       va_start(args, format);
+       NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
+       va_end(args);
+       
+       NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
+       if (callback)
+               [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
+       
+       [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
+       
+       return callbackKey;
+}
+
+/**
+ * Checks if there are unsent commands in the |queuedWrites_| queue and sends
+ * them if it's OK to do so. This will not block.
+ */
+- (void)sendQueuedWrites
+{
+       if (!connected)
+               return;
+       
+       [writeQueueLock_ lock];
+       if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
+       {
+               NSString* command = [queuedWrites_ objectAtIndex:0];
+               
+               // We don't want to block because this is called from the main thread.
+               // |-performSend:| busy waits when the stream is not ready. Bail out
+               // before we do that becuase busy waiting is BAD.
+               if (CFWriteStreamCanAcceptBytes(writeStream_))
+               {
+                       [self performSend:command];
+                       [queuedWrites_ removeObjectAtIndex:0];
+               }
+       }
+       [writeQueueLock_ unlock];
+}
+
+
+/**
+ * Returns the transaction_id from an NSXMLDocument.
+ */
+- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
+{
+       return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
+}
+
+/**
+ * Scans a command string for the transaction ID component. If it is not found,
+ * returns NSNotFound.
+ */
+- (NSInteger)transactionIDFromCommand:(NSString*)command
+{
+       NSRange occurrence = [command rangeOfString:@"-i "];
+       if (occurrence.location == NSNotFound)
+               return NSNotFound;
+       NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
+       return [transaction intValue];
+}
+
+@end
index 80db4f665c7f57320b641e531b0d5e65e7617f8c..edd027b25d51be07ac4092f5d14aa3ad4175a97e 100644 (file)
@@ -19,8 +19,6 @@
 #import "Breakpoint.h"
 #import "StackFrame.h"
 
-@class LoggingController;
-
 @protocol DebuggerProcessorDelegate;
 
 // The DebuggerConnection is the communication layer between the application
        
        // The connection's delegate.
        id <DebuggerProcessorDelegate> delegate;
-
-       // The raw CFSocket on which the two streams are based. Strong.
-       CFSocketRef socket_;
-       
-       // The read stream that is scheduled on the main run loop. Weak.
-       CFReadStreamRef readStream_;
-       
-       // The write stream. Weak.
-       CFWriteStreamRef writeStream_;
-       
-       // An ever-increasing integer that gives each transaction a unique ID for the
-       // debugging engine.
-       NSUInteger transactionID;
-       
-       // The most recently received transaction ID.
-       NSUInteger lastReadTransaction_;
-       
-       // The last transactionID written to the stream.
-       NSUInteger lastWrittenTransaction_;
-       
-       // Callback table. This maps transaction IDs to selectors. When the engine
-       // returns a response to the debugger, we will dispatch the response XML to
-       // the selector, based on transaction_id.
-       NSMutableDictionary* callTable_;
-       
-       // To prevent blocked writing, we enqueue all writes and then wait for the
-       // write stream to tell us it's ready. We store the pending commands in this
-       // array. We use this as a stack (FIFO), with index 0 being first.
-       NSMutableArray* queuedWrites_;
-       
-       // We send queued writes in multiple places, sometimes off a run loop event.
-       // Because of this, we need to ensure that only one client is dequeing and
-       // sending at a time.
-       NSRecursiveLock* writeQueueLock_;
-       
-       // Information about the current read loop. We append to |currentPacket_|
-       // until |currentPacketSize_| has reached |packetSize_|.
-       NSMutableString* currentPacket_;
-       int packetSize_;
-       int currentPacketIndex_;
        
        // A dictionary that maps routingIDs to StackFrame objects.
        NSMutableDictionary* stackFrames_;
index 1d215ddf5dff02305f211cf248f0fa0efd87e559..f4c68a77becbd47b67b98dc64ce3bd2644e3735c 100644 (file)
  * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
-#import <sys/socket.h>
-#import <netinet/in.h>
-
 #import "DebuggerProcessor.h"
 
 #import "AppDelegate.h"
-#import "LoggingController.h"
 
 // GDBpConnection (Private) ////////////////////////////////////////////////////
 
 @interface DebuggerProcessor ()
 @property (readwrite, copy) NSString* status;
-@property (assign) CFSocketRef socket;
-@property (assign) CFReadStreamRef readStream;
-@property NSUInteger lastReadTransaction;
-@property (retain) NSMutableString* currentPacket;
-@property (assign) CFWriteStreamRef writeStream;
-@property NSUInteger lastWrittenTransaction;
-@property (retain) NSMutableArray* queuedWrites;
-
-- (void)connect;
-- (void)close;
-- (void)socketDidAccept;
-- (void)socketDisconnected;
-- (void)readStreamHasData;
-- (void)send:(NSString*)command;
-- (void)performSend:(NSString*)command;
-- (void)errorEncountered:(NSString*)error;
-
-- (void)handleResponse:(NSXMLDocument*)response;
-- (void)handlePacket:(NSString*)packet;
 
 - (void)initReceived:(NSXMLDocument*)response;
 - (void)updateStatus:(NSXMLDocument*)response;
 - (void)variablesReceived:(NSXMLDocument*)response;
 - (void)propertiesReceived:(NSXMLDocument*)response;
 
-- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...;
-
-- (void)sendQueuedWrites;
-
-- (NSString*)escapedURIPath:(NSString*)path;
-- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response;
-- (NSInteger)transactionIDFromCommand:(NSString*)command;
 @end
 
-// CFNetwork Callbacks /////////////////////////////////////////////////////////
-
-void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
-{
-       DebuggerProcessor* connection = (DebuggerProcessor*)connectionRaw;
-       switch (eventType)
-       {
-               case kCFStreamEventHasBytesAvailable:
-                       [connection readStreamHasData];
-                       break;
-                       
-               case kCFStreamEventErrorOccurred:
-               {
-                       CFErrorRef error = CFReadStreamCopyError(stream);
-                       CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
-                       CFReadStreamClose(stream);
-                       CFRelease(stream);
-                       [connection errorEncountered:[[(NSError*)error autorelease] description]];
-                       break;
-               }
-                       
-               case kCFStreamEventEndEncountered:
-                       CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
-                       CFReadStreamClose(stream);
-                       CFRelease(stream);
-                       [connection socketDisconnected];
-                       break;
-       };
-}
-
-void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
-{
-       DebuggerProcessor* connection = (DebuggerProcessor*)connectionRaw;
-       switch (eventType)
-       {
-               case kCFStreamEventCanAcceptBytes:
-                       [connection sendQueuedWrites];
-                       break;
-               
-               case kCFStreamEventErrorOccurred:
-               {
-                       CFErrorRef error = CFWriteStreamCopyError(stream);
-                       CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
-                       CFWriteStreamClose(stream);
-                       CFRelease(stream);
-                       [connection errorEncountered:[[(NSError*)error autorelease] description]];
-                       break;
-               }
-                       
-               case kCFStreamEventEndEncountered:
-                       CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
-                       CFWriteStreamClose(stream);
-                       CFRelease(stream);
-                       [connection socketDisconnected];
-                       break;
-       }
-}
-
-void SocketAcceptCallback(CFSocketRef socket,
-                                                 CFSocketCallBackType callbackType,
-                                                 CFDataRef address,
-                                                 const void* data,
-                                                 void* connectionRaw)
-{
-       assert(callbackType == kCFSocketAcceptCallBack);
-       NSLog(@"SocketAcceptCallback()");
-       
-       DebuggerProcessor* connection = (DebuggerProcessor*)connectionRaw;
-       
-       CFReadStreamRef readStream;
-       CFWriteStreamRef writeStream;
-       
-       // Create the streams on the socket.
-       CFStreamCreatePairWithSocket(kCFAllocatorDefault,
-                                                                *(CFSocketNativeHandle*)data,  // Socket handle.
-                                                                &readStream,  // Read stream in-pointer.
-                                                                &writeStream);  // Write stream in-pointer.
-       
-       // Create struct to register callbacks for the stream.
-       CFStreamClientContext context;
-       context.version = 0;
-       context.info = connection;
-       context.retain = NULL;
-       context.release = NULL;
-       context.copyDescription = NULL;
-       
-       // Set the client of the read stream.
-       CFOptionFlags readFlags =
-               kCFStreamEventOpenCompleted |
-               kCFStreamEventHasBytesAvailable |
-               kCFStreamEventErrorOccurred |
-               kCFStreamEventEndEncountered;
-       if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
-               // Schedule in run loop to do asynchronous communication with the engine.
-               CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
-       else
-               return;
-       
-       // Open the stream now that it's scheduled on the run loop.
-       if (!CFReadStreamOpen(readStream))
-       {
-               CFStreamError error = CFReadStreamGetError(readStream);
-               NSLog(@"error! %@", error);
-               return;
-       }
-       
-       // Set the client of the write stream.
-       CFOptionFlags writeFlags =
-               kCFStreamEventOpenCompleted |
-               kCFStreamEventCanAcceptBytes |
-               kCFStreamEventErrorOccurred |
-               kCFStreamEventEndEncountered;
-       if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
-               // Schedule it in the run loop to receive error information.
-               CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
-       else
-               return;
-       
-       // Open the write stream.
-       if (!CFWriteStreamOpen(writeStream))
-       {
-               CFStreamError error = CFWriteStreamGetError(writeStream);
-               NSLog(@"error! %@", error);
-               return;
-       }
-       
-       connection.readStream = readStream;
-       connection.writeStream = writeStream;
-       [connection socketDidAccept];
-}
-
 // GDBpConnection //////////////////////////////////////////////////////////////
 
 @implementation DebuggerProcessor
-@synthesize socket = socket_;
-@synthesize readStream = readStream_;
-@synthesize lastReadTransaction = lastReadTransaction_;
-@synthesize currentPacket = currentPacket_;
-@synthesize writeStream = writeStream_;
-@synthesize lastWrittenTransaction = lastWrittenTransaction_;
-@synthesize queuedWrites = queuedWrites_;
+
 @synthesize status;
 @synthesize delegate;
 
@@ -219,7 +52,10 @@ void SocketAcceptCallback(CFSocketRef socket,
        {
                port = aPort;
                connected = NO;
-               
+
+               stackFrames_ = [[NSMutableDictionary alloc] init];
+               callbackContext_ = [NSMutableDictionary new];
+
                [[BreakpointManager sharedManager] setConnection:self];
                [self connect];
        }
@@ -232,8 +68,8 @@ void SocketAcceptCallback(CFSocketRef socket,
 - (void)dealloc
 {
        [self close];
-       self.currentPacket = nil;
-       
+       [stackFrames_ release];
+       [callbackContext_ release];
        [super dealloc];
 }
 
@@ -355,348 +191,6 @@ void SocketAcceptCallback(CFSocketRef socket,
        [self sendCommandWithCallback:nil format:@"breakpoint_remove -d %i", [bp debuggerId]];
 }
 
-
-// Socket and Stream Callbacks /////////////////////////////////////////////////
-#pragma mark Callbacks
-
-/**
- * Called by SocketWrapper after the connection is successful. This immediately calls
- * -[SocketWrapper receive] to clear the way for communication, though the information
- * could be useful server information that we don't use right now.
- */
-- (void)socketDidAccept
-{
-       connected = YES;
-       transactionID = 1;
-       stackFrames_ = [[NSMutableDictionary alloc] init];
-       self.queuedWrites = [NSMutableArray array];
-       writeQueueLock_ = [NSRecursiveLock new];
-       callTable_ = [NSMutableDictionary new];
-       callbackContext_ = [NSMutableDictionary new];
-}
-
-/**
- * Receives errors from the SocketWrapper and updates the display
- */
-- (void)errorEncountered:(NSString*)error
-{
-       [delegate errorEncountered:error];
-}
-
-/**
- * Creates, connects to, and schedules a CFSocket.
- */
-- (void)connect
-{
-       // Pass ourselves to the callback so we don't have to use ugly globals.
-       CFSocketContext context;
-       context.version = 0;
-       context.info = self;
-       context.retain = NULL;
-       context.release = NULL;
-       context.copyDescription = NULL;
-       
-       // Create the address structure.
-       struct sockaddr_in address;
-       memset(&address, 0, sizeof(address));
-       address.sin_len = sizeof(address);
-       address.sin_family = AF_INET;
-       address.sin_port = htons(port);
-       address.sin_addr.s_addr = htonl(INADDR_ANY);            
-       
-       // Create the socket signature.
-       CFSocketSignature signature;
-       signature.protocolFamily = PF_INET;
-       signature.socketType = SOCK_STREAM;
-       signature.protocol = IPPROTO_TCP;
-       signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
-       
-       socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
-                                                                                               &signature,  // Socket signature.
-                                                                                               kCFSocketAcceptCallBack,  // Callback types.
-                                                                                               SocketAcceptCallback,  // Callout function pointer.
-                                                                                               &context);  // Context to pass to callout.
-       if (!socket_)
-       {
-               [self errorEncountered:@"Could not open socket."];
-               return;
-       }
-       
-       // Allow old, yet-to-be recycled sockets to be reused.
-       BOOL yes = YES;
-       setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
-       
-       // Schedule the socket on the run loop.
-       CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
-       CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
-       CFRelease(source);
-       
-       self.status = @"Connecting";
-}
-
-/**
- * Closes a socket and releases the ref.
- */
-- (void)close
-{
-       // The socket goes down, so do the streams, which clean themselves up.
-       CFSocketInvalidate(socket_);
-       CFRelease(socket_);
-       [stackFrames_ release];
-       self.queuedWrites = nil;
-       [writeQueueLock_ release];
-       [callTable_ release];
-       [callbackContext_ release];
-}
-
-/**
- * Notification that the socket disconnected.
- */
-- (void)socketDisconnected
-{
-       [self close];
-       [delegate debuggerDisconnected];
-}
-
-/**
- * Callback from the CFReadStream that there is data waiting to be read.
- */
-- (void)readStreamHasData
-{
-       const NSUInteger kBufferSize = 1024;
-       UInt8 buffer[kBufferSize];
-       CFIndex bufferOffset = 0;  // Starting point in |buffer| to work with.
-       CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
-       const char* charBuffer = (const char*)buffer;
-
-       // The read loop works by going through the buffer until all the bytes have
-       // been processed.
-       while (bufferOffset < bytesRead)
-       {
-               // Find the NULL separator, or the end of the string.
-               NSUInteger partLength = 0;
-               for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
-
-               // If there is not a current packet, set some state.
-               if (!self.currentPacket)
-               {
-                       // Read the message header: the size.  This will be |partLength| bytes.
-                       packetSize_ = atoi(charBuffer + bufferOffset);
-                       currentPacketIndex_ = 0;
-                       self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
-                       bufferOffset += partLength + 1;  // Pass over the NULL byte.
-                       continue;  // Spin the loop to begin reading actual data.
-               }
-
-               // Substring the byte stream and append it to the packet string.
-               CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
-                                                                                                                  buffer + bufferOffset,  // Byte pointer, offset by start index.
-                                                                                                                  partLength,  // Length.
-                                                                                                                  kCFStringEncodingUTF8,
-                                                                                                                  true);
-               [self.currentPacket appendString:(NSString*)bufferString];
-               CFRelease(bufferString);
-
-               // Advance counters.
-               currentPacketIndex_ += partLength;
-               bufferOffset += partLength + 1;
-               
-               // If this read finished the packet, handle it and reset.
-               NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
-               if (currentPacketIndex_ >= packetSize_)
-               {
-                       [self handlePacket:[[currentPacket_ retain] autorelease]];
-                       self.currentPacket = nil;
-                       packetSize_ = 0;
-                       currentPacketIndex_ = 0;
-               }
-       }
-}
-
-/**
- * Performs the packet handling of a raw string XML packet. From this point on,
- * the packets are associated with a transaction and are then dispatched.
- */
-- (void)handlePacket:(NSString*)packet
-{
-       // Test if we can convert it into an NSXMLDocument.
-       NSError* error = nil;
-       NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
-       
-       // Try to recover if we encountered an error.
-       if (!xmlTest)
-       {
-               // We do not want to starve the write queue, so manually parse out the
-               // transaction ID.
-               NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
-               if (location.location != NSNotFound)
-               {
-                       NSUInteger start = location.location + location.length;
-                       NSUInteger end = start;
-                       
-                       NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
-                       
-                       // Loop over the characters after the attribute name to extract the ID.
-                       while (end < [currentPacket_ length])
-                       {
-                               unichar c = [currentPacket_ characterAtIndex:end];
-                               if ([numericSet characterIsMember:c])
-                               {
-                                       // If this character is numeric, extend the range to substring.
-                                       ++end;
-                               }
-                               else
-                               {
-                                       if (start == end)
-                                       {
-                                               // If this character is nonnumeric and we have nothing in the
-                                               // range, skip this character.
-                                               ++start;
-                                               ++end;
-                                       }
-                                       else
-                                       {
-                                               // We've moved past the numeric ID so we should stop searching.
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       // If we were able to extract the transaction ID, update the last read.
-                       NSRange substringRange = NSMakeRange(start, end - start);
-                       NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
-                       if ([transactionStr length])
-                               lastReadTransaction_ = [transactionStr intValue];
-               }
-               
-               // Otherwise, assume +1 and hope it works.
-               ++lastReadTransaction_;
-       }
-       else
-       {
-               // See if the transaction can be parsed out.
-               NSInteger transaction = [self transactionIDFromResponse:xmlTest];
-               if (transaction < lastReadTransaction_)
-               {
-                       NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
-                       NSLog(@"out of date transaction %@", packet);
-                       return;
-               }
-               
-               if (transaction != lastWrittenTransaction_)
-                       NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
-               
-               lastReadTransaction_ = transaction;
-       }
-       
-       // Log this receive event.
-       LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
-       LogEntry* log = [logger recordReceive:currentPacket_];
-       log.error = error;
-       log.lastWrittenTransactionID = lastWrittenTransaction_;
-       log.lastReadTransactionID = lastReadTransaction_;
-       
-       // Finally, dispatch the handler for this response.
-       [self handleResponse:[xmlTest autorelease]];    
-}
-
-/**
- * Writes a command into the write stream. If the stream is ready for writing,
- * we do so immediately. If not, the command is queued and will be written
- * when the stream is ready.
- */
-- (void)send:(NSString*)command
-{
-       if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
-               [self performSend:command];
-       else
-               [queuedWrites_ addObject:command];
-       [self sendQueuedWrites];
-}
-
-/**
- * This performs a blocking send. This should ONLY be called when we know we
- * have write access to the stream. We will busy wait in case we don't do a full
- * send.
- */
-- (void)performSend:(NSString*)command
-{
-       // If this is an out-of-date transaction, do not bother sending it.
-       NSInteger transaction = [self transactionIDFromCommand:command];
-       if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
-               return;
-
-       BOOL done = NO;
-       
-       char* string = (char*)[command UTF8String];
-       int stringLength = strlen(string);
-
-       // Busy wait while writing. BAADD. Should background this operation.
-       while (!done)
-       {
-               if (CFWriteStreamCanAcceptBytes(writeStream_))
-               {
-                       // Include the NULL byte in the string when we write.
-                       int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
-                       if (bytesWritten < 0)
-                       {
-                               NSLog(@"write error");
-                       }
-                       // Incomplete write.
-                       else if (bytesWritten < strlen(string))
-                       {
-                               // Adjust the buffer and wait for another chance to write.
-                               stringLength -= bytesWritten;
-                               memmove(string, string + bytesWritten, stringLength);
-                       }
-                       else
-                       {
-                               done = YES;
-                               
-                               // We need to scan the string to find the transactionID.
-                               if (transaction == NSNotFound)
-                               {
-                                       NSLog(@"sent %@ without a transaction ID", command);
-                                       continue;
-                               }
-                               lastWrittenTransaction_ = transaction;
-                       }
-               }
-       }
-
-       // Log this trancation.
-       LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
-       LogEntry* log = [logger recordSend:command];
-       log.lastWrittenTransactionID = lastWrittenTransaction_;
-       log.lastReadTransactionID = lastReadTransaction_;
-}
-
-- (void)handleResponse:(NSXMLDocument*)response
-{
-       // Check and see if there's an error.
-       NSArray* error = [[response rootElement] elementsForName:@"error"];
-       if ([error count] > 0)
-       {
-               NSLog(@"Xdebug error: %@", error);
-               [delegate errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
-       }
-
-       if ([[[response rootElement] name] isEqualToString:@"init"])
-       {
-               [self initReceived:response];
-               return;
-       }
-       
-       NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
-       if (callbackStr)
-       {
-               SEL callback = NSSelectorFromString(callbackStr);
-               [self performSelector:callback withObject:response];
-       }
-       
-       [self sendQueuedWrites];
-}
-
 // Specific Response Handlers //////////////////////////////////////////////////
 #pragma mark Response Handlers
 
@@ -936,57 +430,6 @@ void SocketAcceptCallback(CFSocketRef socket,
        [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
 }
 
-#pragma mark Private
-
-/**
- * This will send a command to the debugger engine. It will append the
- * transaction ID automatically. It accepts a NSString command along with a
- * a variable number of arguments to substitute into the command, a la
- * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
- */
-- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
-{
-       // Collect varargs and format command.
-       va_list args;
-       va_start(args, format);
-       NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
-       va_end(args);
-       
-       NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
-       if (callback)
-               [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
-       
-       [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
-       
-       return callbackKey;
-}
-
-/**
- * Checks if there are unsent commands in the |queuedWrites_| queue and sends
- * them if it's OK to do so. This will not block.
- */
-- (void)sendQueuedWrites
-{
-       if (!connected)
-               return;
-       
-       [writeQueueLock_ lock];
-       if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
-       {
-               NSString* command = [queuedWrites_ objectAtIndex:0];
-               
-               // We don't want to block because this is called from the main thread.
-               // |-performSend:| busy waits when the stream is not ready. Bail out
-               // before we do that becuase busy waiting is BAD.
-               if (CFWriteStreamCanAcceptBytes(writeStream_))
-               {
-                       [self performSend:command];
-                       [queuedWrites_ removeObjectAtIndex:0];
-               }
-       }
-       [writeQueueLock_ unlock];
-}
-
 /**
  * Given a file path, this returns a file:// URI and escapes any spaces for the
  * debugger engine.
@@ -1009,25 +452,4 @@ void SocketAcceptCallback(CFSocketRef socket,
        return urlString;
 }
 
-/**
- * Returns the transaction_id from an NSXMLDocument.
- */
-- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
-{
-       return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
-}
-
-/**
- * Scans a command string for the transaction ID component. If it is not found,
- * returns NSNotFound.
- */
-- (NSInteger)transactionIDFromCommand:(NSString*)command
-{
-       NSRange occurrence = [command rangeOfString:@"-i "];
-       if (occurrence.location == NSNotFound)
-               return NSNotFound;
-       NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
-       return [transaction intValue];
-}
-
 @end