From 3aa0530be3d6a83c4bd55e2780b4f2858ae960ed Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Tue, 18 Jun 2013 01:05:46 -0400 Subject: [PATCH] Write ProtocolClient, the layer that talks XML on top of a MessageQueue. This is the other piece of the split NetworkConnection class. --- MacGDBp.xcodeproj/project.pbxproj | 6 + Source/ProtocolClient.h | 70 ++++++++++++ Source/ProtocolClient.m | 175 ++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 Source/ProtocolClient.h create mode 100644 Source/ProtocolClient.m diff --git a/MacGDBp.xcodeproj/project.pbxproj b/MacGDBp.xcodeproj/project.pbxproj index 3c91f76..b1fc552 100644 --- a/MacGDBp.xcodeproj/project.pbxproj +++ b/MacGDBp.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 1EC6965812BBC6A700A8D984 /* modp_b64.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6965212BBC6A700A8D984 /* modp_b64.cc */; }; 1EDA9CF812DD13B300596211 /* BSLineNumberRulerView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1EDA9CF712DD13B300596211 /* BSLineNumberRulerView.mm */; }; 1EEBE842176FEA80003622C3 /* MessageQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EEBE841176FEA80003622C3 /* MessageQueue.m */; }; + 1EEBE845176FFE04003622C3 /* ProtocolClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EEBE844176FFE04003622C3 /* ProtocolClient.m */; }; 1EEBFBE50D34C793008F835B /* Debugger.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1EEBFBE30D34C793008F835B /* Debugger.xib */; }; 1EEBFC2B0D358EBD008F835B /* StepIn.png in Resources */ = {isa = PBXBuildFile; fileRef = 1EEBFC2A0D358EBD008F835B /* StepIn.png */; }; 1EEBFC370D358F1B008F835B /* StepOut.png in Resources */ = {isa = PBXBuildFile; fileRef = 1EEBFC360D358F1B008F835B /* StepOut.png */; }; @@ -124,6 +125,8 @@ 1EDA9CF712DD13B300596211 /* BSLineNumberRulerView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BSLineNumberRulerView.mm; path = Source/BSLineNumberRulerView.mm; sourceTree = ""; }; 1EEBE840176FEA80003622C3 /* MessageQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MessageQueue.h; path = Source/MessageQueue.h; sourceTree = ""; }; 1EEBE841176FEA80003622C3 /* MessageQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MessageQueue.m; path = Source/MessageQueue.m; sourceTree = ""; }; + 1EEBE843176FFE04003622C3 /* ProtocolClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProtocolClient.h; path = Source/ProtocolClient.h; sourceTree = ""; }; + 1EEBE844176FFE04003622C3 /* ProtocolClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProtocolClient.m; path = Source/ProtocolClient.m; sourceTree = ""; }; 1EEBFBE40D34C793008F835B /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/Debugger.xib; sourceTree = ""; }; 1EEBFC2A0D358EBD008F835B /* StepIn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = StepIn.png; path = Icons/StepIn.png; sourceTree = ""; }; 1EEBFC360D358F1B008F835B /* StepOut.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = StepOut.png; path = Icons/StepOut.png; sourceTree = ""; }; @@ -319,6 +322,8 @@ children = ( 1EEBE840176FEA80003622C3 /* MessageQueue.h */, 1EEBE841176FEA80003622C3 /* MessageQueue.m */, + 1EEBE843176FFE04003622C3 /* ProtocolClient.h */, + 1EEBE844176FFE04003622C3 /* ProtocolClient.m */, ); name = Protocol; sourceTree = ""; @@ -488,6 +493,7 @@ 1E108E40136CC8B9002E34E0 /* EvalController.m in Sources */, 1E109019136DD92D002E34E0 /* StripLineBreaksValueTransformer.m in Sources */, 1EEBE842176FEA80003622C3 /* MessageQueue.m in Sources */, + 1EEBE845176FFE04003622C3 /* ProtocolClient.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/ProtocolClient.h b/Source/ProtocolClient.h new file mode 100644 index 0000000..982edad --- /dev/null +++ b/Source/ProtocolClient.h @@ -0,0 +1,70 @@ +/* + * MacGDBp + * Copyright (c) 2013, 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 + +#import "MessageQueue.h" + +@protocol ProtocolClientDelegate; + +// ProtocolClient sends string commands to a DBGP +// debugger engine and receives XML packets in response. This class ensures +// proper sequencing of the messages. +@interface ProtocolClient : NSObject { + @private + MessageQueue* _messageQueue; + + NSRecursiveLock* _lock; + NSInteger _nextID; + + NSInteger _lastReadID; + NSInteger _lastWrittenID; + + NSObject* _delegate; + NSThread* _delegateThread; +} + +- (id)initWithDelegate:(NSObject*)delegate; + +- (BOOL)isConnected; + +- (void)connectOnPort:(NSUInteger)port; +- (void)disconnect; + +// This sends the given command format to the debugger. This method is thread +// safe and schedules the request on the |runLoop_|. +- (NSNumber*)sendCommandWithFormat:(NSString*)format, ...; + +// Sends a command to the debugger. The command must have a substring |{txn}| +// within it, which will be replaced with the transaction ID. Use this if +// |-sendCommandWithFormat:|'s insertion of the transaction ID is incorrect. +- (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ...; + +- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response; +- (NSInteger)transactionIDFromCommand:(NSString*)command; + +@end + +// Delegate //////////////////////////////////////////////////////////////////// + +// All methods of the protocol client are dispatched to the thread on which the +// ProtocolClient was created. +@protocol ProtocolClientDelegate +- (void)debuggerEngineConnected:(ProtocolClient*)client; +- (void)debuggerEngineDisconnected:(ProtocolClient*)client; + +- (void)debuggerEngine:(ProtocolClient*)client receivedMessage:(NSXMLDocument*)message; +@end diff --git a/Source/ProtocolClient.m b/Source/ProtocolClient.m new file mode 100644 index 0000000..f8d386c --- /dev/null +++ b/Source/ProtocolClient.m @@ -0,0 +1,175 @@ +/* + * MacGDBp + * Copyright (c) 2013, 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 "ProtocolClient.h" + +@interface ProtocolClient (Private) +- (void)postReceivedMessage:(NSXMLDocument*)message; +@end + +@implementation ProtocolClient + +- (id)initWithDelegate:(NSObject*)delegate { + if ((self = [super init])) { + _delegate = delegate; + _delegateThread = [NSThread currentThread]; + _lock = [[NSRecursiveLock alloc] init]; + } + return self; +} + +- (void)dealloc { + [_lock release]; + [super dealloc]; +} + +- (BOOL)isConnected { + return [_messageQueue isConnected]; +} + +- (void)connectOnPort:(NSUInteger)port { + assert(!_messageQueue); + _messageQueue = [[MessageQueue alloc] initWithPort:port delegate:self]; + [_messageQueue connect]; +} + +- (void)disconnect { + [_messageQueue disconnect]; +} + +- (NSNumber*)sendCommandWithFormat:(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:_nextID++]; + NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]; + + [_messageQueue sendMessage:taggedCommand]; + return callbackKey; +} + +- (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ... { + // Collect varargs and format command. + va_list args; + va_start(args, format); + NSString* command = [[[NSString alloc] initWithFormat:format arguments:args] autorelease]; + va_end(args); + + NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++]; + NSString* taggedCommand = [command stringByReplacingOccurrencesOfString:@"{txn}" + withString:[callbackKey stringValue]]; + + [_messageQueue sendMessage:taggedCommand]; + return callbackKey; +} + +- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response { + return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue]; +} + +- (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]; +} + +// MessageQueueDelegate //////////////////////////////////////////////////////// + +- (void)messageQueueError:(NSError*)error { + NSLog(@"error = %@", error); +} + +- (void)clientDidConnect:(MessageQueue*)queue { + [_lock lock]; + _nextID = 0; + _lastReadID = 0; + _lastWrittenID = 0; + [_lock unlock]; + + [_delegate performSelector:@selector(debuggerEngineConnected:) + onThread:_delegateThread + withObject:self + waitUntilDone:NO]; +} + +- (void)clientDidDisconnect:(MessageQueue*)queue { + [_delegate performSelector:@selector(debuggerEngineDisconnected:) + onThread:_delegateThread + withObject:self + waitUntilDone:NO]; +} + +// If the write stream is ready, the delegate controls whether or not the next +// pending message should be sent via the result of this method. +- (BOOL)shouldSendMessage { + [_lock lock]; + BOOL r = _lastReadID >= _lastWrittenID; + [_lock unlock]; + return r; +} + +// Callback for when a message has been sent. +- (void)didSendMessage:(NSString*)message { + NSInteger tag = [self transactionIDFromCommand:message]; + [_lock lock]; + _lastWrittenID = tag; + [_lock unlock]; +} + +// Callback with the message content when one has been receieved. +- (void)didReceiveMessage:(NSString*)message { + // Test if we can convert it into an NSXMLDocument. + NSError* error = nil; + NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message + options:NSXMLDocumentTidyXML + error:&error]; + if (error) { + [self messageQueueError:error]; + return; + } + + // Validate the transaction. + NSInteger transaction = [self transactionIDFromResponse:xml]; + if (transaction < _lastReadID) { + NSLog(@"Transaction #%d is out of date (lastRead = %d). Dropping packet: %@", + transaction, _lastReadID, message); + return; + } + if (transaction != _lastWrittenID) { + NSLog(@"Transaction #%d received out of order. lastRead = %d, lastWritten = %d. Continuing.", + transaction, _lastReadID, _lastWrittenID); + } + + _lastReadID = transaction; + + [self performSelector:@selector(postReceivedMessage:) + onThread:_delegateThread + withObject:xml + waitUntilDone:NO]; +} + +// Private ///////////////////////////////////////////////////////////////////// + +- (void)postReceivedMessage:(NSXMLDocument*)message { + [_delegate debuggerEngine:self receivedMessage:message]; +} + +@end -- 2.22.5