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 return [_messageQueue isConnected
];
54 - (void)connectOnPort
:(NSUInteger
)port
{
55 assert(!_messageQueue
);
56 _messageQueue
= [[MessageQueue alloc
] initWithPort
:port delegate
:self];
57 [_messageQueue connect
];
61 [_messageQueue disconnect
];
64 - (void)sendCommandWithFormat
:(NSString
*)format
, ...
{
65 // Collect varargs and format command.
67 va_start(args
, format
);
68 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
71 [self sendCommandWithFormat
:command handler
:^
(NSXMLDocument
* message
){}];
74 - (void)sendCommandWithFormat
:(NSString
*)format
75 handler
:(ProtocolClientMessageHandler
)handler
, ...
{
76 // Collect varargs and format command.
78 va_start(args
, handler
);
79 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
82 int transaction
= _nextID
++;
83 NSString
* taggedCommand
= [NSString stringWithFormat
:@
"%@ -i %d", command
, transaction
];
85 assert(_messageQueue
);
86 [_dispatchTable setObject
:[handler copy
] forKey
:@
(transaction
)];
87 [_messageQueue sendMessage
:taggedCommand
];
90 - (void)sendCustomCommandWithFormat
:(NSString
*)format
91 handler
:(ProtocolClientMessageHandler
)handler
, ...
{
92 // Collect varargs and format command.
94 va_start(args
, handler
);
95 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
98 int transaction
= _nextID
++;
99 NSString
* taggedCommand
=
100 [command stringByReplacingOccurrencesOfString
:@
"{txn}"
101 withString
:[NSString stringWithFormat
:@
"%d", transaction
]];
103 assert(_messageQueue
);
104 [_dispatchTable setObject
:[handler copy
] forKey
:@
(transaction
)];
105 [_messageQueue sendMessage
:taggedCommand
];
109 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
{
110 return [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
113 - (NSInteger
)transactionIDFromCommand
:(NSString
*)command
{
114 NSRange occurrence
= [command rangeOfString
:@
"-i "];
115 if (occurrence.location
== NSNotFound
)
117 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
118 return [transaction intValue
];
121 + (NSString
*)escapedFilePathURI
:(NSString
*)path
{
122 // Custon GDBp paths are fine.
123 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
126 // Create a temporary URL that will escape all the nasty characters.
127 NSURL
* url
= [NSURL fileURLWithPath
:path
];
128 NSString
* urlString
= [url absoluteString
];
130 // Remove the host because this is a file:// URL;
131 NSString
* host
= [url host
];
133 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
135 // Escape % for use in printf-style NSString formatters.
136 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
140 // MessageQueueDelegate ////////////////////////////////////////////////////////
142 - (void)messageQueue
:(MessageQueue
*)queue error
:(NSError
*)error
{
143 NSLog(@
"error = %@", error
);
146 - (void)messageQueueDidConnect
:(MessageQueue
*)queue
{
151 [_delegate debuggerEngineConnected
:self];
154 - (void)messageQueueDidDisconnect
:(MessageQueue
*)queue
{
156 [_dispatchTable removeAllObjects
];
157 [_delegate debuggerEngineDisconnected
:self];
160 // Callback for when a message has been sent.
161 - (void)messageQueue
:(MessageQueue
*)queue didSendMessage
:(NSString
*)message
{
162 NSInteger tag
= [self transactionIDFromCommand
:message
];
163 _lastWrittenID
= tag
;
165 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
166 LogEntry
* entry
= [LogEntry newSendEntry
:message
];
167 entry.lastReadTransactionID
= _lastReadID
;
168 entry.lastWrittenTransactionID
= _lastWrittenID
;
169 [logger recordEntry
:entry
];
172 // Callback with the message content when one has been receieved.
173 - (void)messageQueue
:(MessageQueue
*)queue didReceiveMessage
:(NSString
*)message
{
174 // Record this message in the transaction log.
175 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
176 LogEntry
* entry
= [LogEntry newReceiveEntry
:message
];
177 entry.lastReadTransactionID
= _lastReadID
;
178 entry.lastWrittenTransactionID
= _lastWrittenID
;
179 [logger recordEntry
:entry
];
181 // Parse the XML and test for errors.
182 NSError
* error
= nil;
183 NSXMLDocument
* xml
= [[NSXMLDocument alloc
] initWithXMLString
:message
184 options
:NSXMLDocumentTidyXML
187 [self messageQueue
:queue error
:error
];
190 NSInteger transactionID
= [self transactionIDFromResponse
:xml
];
192 _lastReadID
= transactionID
;
193 entry.lastReadTransactionID
= _lastReadID
;
195 if ([[[xml rootElement
] elementsForName
:@
"error"] count
] > 0) {
196 // Handle back-end errors.
197 [_delegate protocolClient
:self receivedErrorMessage
:xml
];
198 } else if ([[[xml rootElement
] name
] isEqualToString
:@
"init"]) {
199 // Handle the initial connection message.
200 [_delegate protocolClient
:self receivedInitialMessage
:xml
];
202 // Dispatch the handler for the message.
203 ProtocolClientMessageHandler handler
= [_dispatchTable objectForKey
:@
(transactionID
)];
205 NSLog(@
"Could not dispatch handler for transaction %ld: %@", transactionID
, message
);
209 [_dispatchTable removeObjectForKey
:@
(transactionID
)];