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 - (void)sendCustomCommandWithFormat
:(NSString
*)format
101 handler
:(ProtocolClientMessageHandler
)handler
, ...
{
102 // Collect varargs and format command.
104 va_start(args
, handler
);
105 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
108 int transaction
= _nextID
++;
109 NSString
* taggedCommand
=
110 [command stringByReplacingOccurrencesOfString
:@
"{txn}"
111 withString
:[NSString stringWithFormat
:@
"%d", transaction
]];
113 assert(_messageQueue
);
114 [_dispatchTable setObject
:[[handler copy
] autorelease
] forKey
:@
(transaction
)];
115 [_messageQueue sendMessage
:taggedCommand
];
119 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
{
120 return [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
123 - (NSInteger
)transactionIDFromCommand
:(NSString
*)command
{
124 NSRange occurrence
= [command rangeOfString
:@
"-i "];
125 if (occurrence.location
== NSNotFound
)
127 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
128 return [transaction intValue
];
131 + (NSString
*)escapedFilePathURI
:(NSString
*)path
{
132 // Custon GDBp paths are fine.
133 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
136 // Create a temporary URL that will escape all the nasty characters.
137 NSURL
* url
= [NSURL fileURLWithPath
:path
];
138 NSString
* urlString
= [url absoluteString
];
140 // Remove the host because this is a file:// URL;
141 NSString
* host
= [url host
];
143 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
145 // Escape % for use in printf-style NSString formatters.
146 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
150 // MessageQueueDelegate ////////////////////////////////////////////////////////
152 - (void)messageQueue
:(MessageQueue
*)queue error
:(NSError
*)error
{
153 NSLog(@
"error = %@", error
);
156 - (void)messageQueueDidConnect
:(MessageQueue
*)queue
{
161 [_delegate debuggerEngineConnected
:self];
164 - (void)messageQueueDidDisconnect
:(MessageQueue
*)queue
{
165 [_messageQueue release
];
167 [_dispatchTable removeAllObjects
];
168 [_delegate debuggerEngineDisconnected
:self];
171 // Callback for when a message has been sent.
172 - (void)messageQueue
:(MessageQueue
*)queue didSendMessage
:(NSString
*)message
{
173 NSInteger tag
= [self transactionIDFromCommand
:message
];
174 _lastWrittenID
= tag
;
176 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
177 LogEntry
* entry
= [LogEntry newSendEntry
:message
];
178 entry.lastReadTransactionID
= _lastReadID
;
179 entry.lastWrittenTransactionID
= _lastWrittenID
;
180 [logger recordEntry
:entry
];
183 // Callback with the message content when one has been receieved.
184 - (void)messageQueue
:(MessageQueue
*)queue didReceiveMessage
:(NSString
*)message
{
185 // Record this message in the transaction log.
186 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
187 LogEntry
* entry
= [LogEntry newReceiveEntry
:message
];
188 entry.lastReadTransactionID
= _lastReadID
;
189 entry.lastWrittenTransactionID
= _lastWrittenID
;
190 [logger recordEntry
:entry
];
192 // Parse the XML and test for errors.
193 NSError
* error
= nil;
194 NSXMLDocument
* xml
= [[NSXMLDocument alloc
] initWithXMLString
:message
195 options
:NSXMLDocumentTidyXML
198 [self messageQueue
:queue error
:error
];
201 int transactionID
= [self transactionIDFromResponse
:xml
];
203 _lastReadID
= transactionID
;
204 entry.lastReadTransactionID
= _lastReadID
;
206 if ([[[xml rootElement
] elementsForName
:@
"error"] count
] > 0) {
207 // Handle back-end errors.
208 [_delegate protocolClient
:self receivedErrorMessage
:xml
];
209 } else if ([[[xml rootElement
] name
] isEqualToString
:@
"init"]) {
210 // Handle the initial connection message.
211 [_delegate protocolClient
:self receivedInitialMessage
:xml
];
213 // Dispatch the handler for the message.
214 ProtocolClientMessageHandler handler
= [_dispatchTable objectForKey
:@
(transactionID
)];
217 [_dispatchTable removeObjectForKey
:@
(transactionID
)];
219 // TODO(rsesek): Remove this path once the backend rewrite is complete.
220 [_delegate debuggerEngine
:self receivedMessage
:xml
];