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 - (void)sendCommandWithFormat
:(NSString
*)format
, ...
{
70 // Collect varargs and format command.
72 va_start(args
, format
);
73 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
76 [self sendCommandWithFormat
:command handler
:^
(NSXMLDocument
* message
){}];
79 - (void)sendCommandWithFormat
:(NSString
*)format
80 handler
:(ProtocolClientMessageHandler
)handler
, ...
{
81 // Collect varargs and format command.
83 va_start(args
, handler
);
84 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
87 int transaction
= _nextID
++;
88 NSString
* taggedCommand
= [NSString stringWithFormat
:@
"%@ -i %d", [command autorelease
], transaction
];
90 assert(_messageQueue
);
91 [_dispatchTable setObject
:[[handler copy
] autorelease
] forKey
:@
(transaction
)];
92 [_messageQueue sendMessage
:taggedCommand
];
95 - (void)sendCustomCommandWithFormat
:(NSString
*)format
96 handler
:(ProtocolClientMessageHandler
)handler
, ...
{
97 // Collect varargs and format command.
99 va_start(args
, handler
);
100 NSString
* command
= [[[NSString alloc
] initWithFormat
:format arguments
:args
] autorelease
];
103 int transaction
= _nextID
++;
104 NSString
* taggedCommand
=
105 [command stringByReplacingOccurrencesOfString
:@
"{txn}"
106 withString
:[NSString stringWithFormat
:@
"%d", transaction
]];
108 assert(_messageQueue
);
109 [_dispatchTable setObject
:[[handler copy
] autorelease
] forKey
:@
(transaction
)];
110 [_messageQueue sendMessage
:taggedCommand
];
114 - (NSInteger
)transactionIDFromResponse
:(NSXMLDocument
*)response
{
115 return [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
118 - (NSInteger
)transactionIDFromCommand
:(NSString
*)command
{
119 NSRange occurrence
= [command rangeOfString
:@
"-i "];
120 if (occurrence.location
== NSNotFound
)
122 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
123 return [transaction intValue
];
126 + (NSString
*)escapedFilePathURI
:(NSString
*)path
{
127 // Custon GDBp paths are fine.
128 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
131 // Create a temporary URL that will escape all the nasty characters.
132 NSURL
* url
= [NSURL fileURLWithPath
:path
];
133 NSString
* urlString
= [url absoluteString
];
135 // Remove the host because this is a file:// URL;
136 NSString
* host
= [url host
];
138 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
140 // Escape % for use in printf-style NSString formatters.
141 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
145 // MessageQueueDelegate ////////////////////////////////////////////////////////
147 - (void)messageQueue
:(MessageQueue
*)queue error
:(NSError
*)error
{
148 NSLog(@
"error = %@", error
);
151 - (void)messageQueueDidConnect
:(MessageQueue
*)queue
{
156 [_delegate debuggerEngineConnected
:self];
159 - (void)messageQueueDidDisconnect
:(MessageQueue
*)queue
{
160 [_messageQueue release
];
162 [_dispatchTable removeAllObjects
];
163 [_delegate debuggerEngineDisconnected
:self];
166 // Callback for when a message has been sent.
167 - (void)messageQueue
:(MessageQueue
*)queue didSendMessage
:(NSString
*)message
{
168 NSInteger tag
= [self transactionIDFromCommand
:message
];
169 _lastWrittenID
= tag
;
171 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
172 LogEntry
* entry
= [LogEntry newSendEntry
:message
];
173 entry.lastReadTransactionID
= _lastReadID
;
174 entry.lastWrittenTransactionID
= _lastWrittenID
;
175 [logger recordEntry
:[entry autorelease
]];
178 // Callback with the message content when one has been receieved.
179 - (void)messageQueue
:(MessageQueue
*)queue didReceiveMessage
:(NSString
*)message
{
180 // Record this message in the transaction log.
181 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
182 LogEntry
* entry
= [LogEntry newReceiveEntry
:message
];
183 entry.lastReadTransactionID
= _lastReadID
;
184 entry.lastWrittenTransactionID
= _lastWrittenID
;
185 [logger recordEntry
:[entry autorelease
]];
187 // Parse the XML and test for errors.
188 NSError
* error
= nil;
189 NSXMLDocument
* xml
= [[[NSXMLDocument alloc
] initWithXMLString
:message
190 options
:NSXMLDocumentTidyXML
191 error
:&error
] autorelease
];
193 [self messageQueue
:queue error
:error
];
196 NSInteger transactionID
= [self transactionIDFromResponse
:xml
];
198 _lastReadID
= transactionID
;
199 entry.lastReadTransactionID
= _lastReadID
;
201 if ([[[xml rootElement
] elementsForName
:@
"error"] count
] > 0) {
202 // Handle back-end errors.
203 [_delegate protocolClient
:self receivedErrorMessage
:xml
];
204 } else if ([[[xml rootElement
] name
] isEqualToString
:@
"init"]) {
205 // Handle the initial connection message.
206 [_delegate protocolClient
:self receivedInitialMessage
:xml
];
208 // Dispatch the handler for the message.
209 ProtocolClientMessageHandler handler
= [_dispatchTable objectForKey
:@
(transactionID
)];
211 [_dispatchTable removeObjectForKey
:@
(transactionID
)];