3 * Copyright (c) 2013, Blue Static <http://www.bluestatic.org>
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 #import "ProtocolClient.h"
19 #import "AppDelegate.h"
20 #import "LoggingController.h"
22 @implementation ProtocolClient
{
23 // The object responsible for the actual communication with the debug server.
24 MessageQueue
* _messageQueue
;
26 // The delegate of this class, which receives high-level messages about the
27 // state of the debugger.
28 id<ProtocolClientDelegate
> _delegate
; // weak
30 // A map between transaction ID and handler block for that message.
31 NSMutableDictionary
<NSNumber
*, ProtocolClientMessageHandler
>* _dispatchTable
;
33 // The next transaction ID to assign.
36 // Records the last read and written transaction IDs. These are only used in
37 // creating LogEntry objects.
38 NSInteger _lastReadID
;
39 NSInteger _lastWrittenID
;
42 - (id)initWithDelegate
:(id<ProtocolClientDelegate
>)delegate
{
43 if ((self = [super init
])) {
45 _dispatchTable
= [[NSMutableDictionary alloc
] init
];
51 [_dispatchTable release
];
56 return [_messageQueue isConnected
];
59 - (void)connectOnPort
:(NSUInteger
)port
{
60 assert(!_messageQueue
);
61 _messageQueue
= [[MessageQueue alloc
] initWithPort
:port delegate
:self];
62 [_messageQueue connect
];
66 [_messageQueue disconnect
];
69 - (NSNumber
*)sendCommandWithFormat
:(NSString
*)format
, ...
{
70 // Collect varargs and format command.
72 va_start(args
, format
);
73 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
76 NSNumber
* callbackKey
= [NSNumber numberWithInt
:_nextID
++];
77 NSString
* taggedCommand
= [NSString stringWithFormat
:@
"%@ -i %@", [command autorelease
], callbackKey
];
79 assert(_messageQueue
);
80 [_messageQueue sendMessage
:taggedCommand
];
84 - (void)sendCommandWithFormat
:(NSString
*)format
85 handler
:(ProtocolClientMessageHandler
)handler
, ...
{
86 // Collect varargs and format command.
88 va_start(args
, handler
);
89 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
92 int transaction
= _nextID
++;
93 NSString
* taggedCommand
= [NSString stringWithFormat
:@
"%@ -i %d", [command autorelease
], transaction
];
95 assert(_messageQueue
);
96 [_dispatchTable setObject
:[[handler copy
] autorelease
] forKey
:@
(transaction
)];
97 [_messageQueue sendMessage
:taggedCommand
];
100 - (NSNumber
*)sendCustomCommandWithFormat
:(NSString
*)format
, ...
{
101 // Collect varargs and format command.
103 va_start(args
, format
);
104 NSString
* command
= [[[NSString alloc
] initWithFormat
:format arguments
:args
] autorelease
];
107 NSNumber
* callbackKey
= [NSNumber numberWithInt
:_nextID
++];
108 NSString
* taggedCommand
= [command stringByReplacingOccurrencesOfString
:@
"{txn}"
109 withString
:[callbackKey stringValue
]];
111 [_messageQueue sendMessage
:taggedCommand
];
115 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
{
116 return [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
119 - (NSInteger
)transactionIDFromCommand
:(NSString
*)command
{
120 NSRange occurrence
= [command rangeOfString
:@
"-i "];
121 if (occurrence.location
== NSNotFound
)
123 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
124 return [transaction intValue
];
127 + (NSString
*)escapedFilePathURI
:(NSString
*)path
{
128 // Custon GDBp paths are fine.
129 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
132 // Create a temporary URL that will escape all the nasty characters.
133 NSURL
* url
= [NSURL fileURLWithPath
:path
];
134 NSString
* urlString
= [url absoluteString
];
136 // Remove the host because this is a file:// URL;
137 NSString
* host
= [url host
];
139 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
141 // Escape % for use in printf-style NSString formatters.
142 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
146 // MessageQueueDelegate ////////////////////////////////////////////////////////
148 - (void)messageQueue
:(MessageQueue
*)queue error
:(NSError
*)error
{
149 NSLog(@
"error = %@", error
);
152 - (void)messageQueueDidConnect
:(MessageQueue
*)queue
{
157 [_delegate debuggerEngineConnected
:self];
160 - (void)messageQueueDidDisconnect
:(MessageQueue
*)queue
{
161 [_messageQueue release
];
163 [_dispatchTable removeAllObjects
];
164 [_delegate debuggerEngineDisconnected
:self];
167 // Callback for when a message has been sent.
168 - (void)messageQueue
:(MessageQueue
*)queue didSendMessage
:(NSString
*)message
{
169 NSInteger tag
= [self transactionIDFromCommand
:message
];
170 _lastWrittenID
= tag
;
172 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
173 LogEntry
* entry
= [LogEntry newSendEntry
:message
];
174 entry.lastReadTransactionID
= _lastReadID
;
175 entry.lastWrittenTransactionID
= _lastWrittenID
;
176 [logger recordEntry
:entry
];
179 // Callback with the message content when one has been receieved.
180 - (void)messageQueue
:(MessageQueue
*)queue didReceiveMessage
:(NSString
*)message
{
181 // Record this message in the transaction log.
182 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
183 LogEntry
* entry
= [LogEntry newReceiveEntry
:message
];
184 entry.lastReadTransactionID
= _lastReadID
;
185 entry.lastWrittenTransactionID
= _lastWrittenID
;
186 [logger recordEntry
:entry
];
188 // Parse the XML and test for errors.
189 NSError
* error
= nil;
190 NSXMLDocument
* xml
= [[NSXMLDocument alloc
] initWithXMLString
:message
191 options
:NSXMLDocumentTidyXML
194 [self messageQueue
:queue error
:error
];
197 int transactionID
= [self transactionIDFromResponse
:xml
];
199 _lastReadID
= transactionID
;
200 entry.lastReadTransactionID
= _lastReadID
;
202 if ([[[xml rootElement
] elementsForName
:@
"error"] count
] > 0) {
203 // Handle back-end errors.
204 [_delegate protocolClient
:self receivedErrorMessage
:xml
];
205 } else if ([[[xml rootElement
] name
] isEqualToString
:@
"init"]) {
206 // Handle the initial connection message.
207 [_delegate protocolClient
:self receivedInitialMessage
:xml
];
209 // Dispatch the handler for the message.
210 ProtocolClientMessageHandler handler
= [_dispatchTable objectForKey
:@
(transactionID
)];
213 [_dispatchTable removeObjectForKey
:@
(transactionID
)];
215 // TODO(rsesek): Remove this path once the backend rewrite is complete.
216 [_delegate debuggerEngine
:self receivedMessage
:xml
];