From a7dab683c254a2f3a56850efae853523f2360fd7 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 31 May 2010 19:44:04 -0400 Subject: [PATCH] Add a new DebuggerConnection that separates out the socket logic from the handling logic. 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 | 6 + Source/DebuggerConnection.h | 93 +++++ Source/DebuggerConnection.m | 593 ++++++++++++++++++++++++++++++ Source/DebuggerProcessor.h | 42 --- Source/DebuggerProcessor.m | 592 +---------------------------- 5 files changed, 699 insertions(+), 627 deletions(-) create mode 100644 Source/DebuggerConnection.h create mode 100644 Source/DebuggerConnection.m diff --git a/MacGDBp.xcodeproj/project.pbxproj b/MacGDBp.xcodeproj/project.pbxproj index d8dcc95..d65599a 100644 --- a/MacGDBp.xcodeproj/project.pbxproj +++ b/MacGDBp.xcodeproj/project.pbxproj @@ -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 = ""; }; 1E02C5F40C610724006F1752 /* DebuggerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DebuggerController.h; path = Source/DebuggerController.h; sourceTree = ""; }; 1E02C5F50C610724006F1752 /* DebuggerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerController.m; path = Source/DebuggerController.m; sourceTree = ""; }; + 1E0724E111B47BCC0017AD3C /* DebuggerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DebuggerConnection.h; path = Source/DebuggerConnection.h; sourceTree = ""; }; + 1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerConnection.m; path = Source/DebuggerConnection.m; sourceTree = ""; }; 1E0AFBB80FC2518700C67031 /* HUDIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = HUDIcon.png; path = Icons/HUDIcon.png; sourceTree = ""; }; 1E1E53020DF9B89800D334F9 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/Breakpoints.xib; sourceTree = ""; }; 1E35FC750C6579CA0030F527 /* NSXMLElementAdditions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = NSXMLElementAdditions.m; path = Source/NSXMLElementAdditions.m; sourceTree = ""; }; @@ -202,6 +205,8 @@ 1E1E52C10DF9B1FB00D334F9 /* Connection */ = { isa = PBXGroup; children = ( + 1E0724E111B47BCC0017AD3C /* DebuggerConnection.h */, + 1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */, 1E02C56F0C610158006F1752 /* DebuggerProcessor.h */, 1E02C5700C610158006F1752 /* DebuggerProcessor.m */, 1E35FFB00C65A74C0030F527 /* NSXMLElementAdditions.h */, @@ -413,6 +418,7 @@ 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 index 0000000..1343d1f --- /dev/null +++ b/Source/DebuggerConnection.h @@ -0,0 +1,93 @@ +/* + * MacGDBp + * Copyright (c) 2007 - 2010, Blue Static + * + * 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 + +@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 index 0000000..190f15d --- /dev/null +++ b/Source/DebuggerConnection.m @@ -0,0 +1,593 @@ +/* + * MacGDBp + * Copyright (c) 2007 - 2010, Blue Static + * + * 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 +#import + +#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 diff --git a/Source/DebuggerProcessor.h b/Source/DebuggerProcessor.h index 80db4f6..edd027b 100644 --- a/Source/DebuggerProcessor.h +++ b/Source/DebuggerProcessor.h @@ -19,8 +19,6 @@ #import "Breakpoint.h" #import "StackFrame.h" -@class LoggingController; - @protocol DebuggerProcessorDelegate; // The DebuggerConnection is the communication layer between the application @@ -43,46 +41,6 @@ // The connection's delegate. id 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_; diff --git a/Source/DebuggerProcessor.m b/Source/DebuggerProcessor.m index 1d215dd..f4c68a7 100644 --- a/Source/DebuggerProcessor.m +++ b/Source/DebuggerProcessor.m @@ -14,37 +14,14 @@ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -#import -#import - #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; @@ -56,156 +33,12 @@ - (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 -- 2.22.5