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 // The backend will interpret this custom scheme.
123 if ([path hasPrefix
:@
"gdbp://"])
125 return [[NSURL fileURLWithPath
:path
] absoluteString
];
128 // MessageQueueDelegate ////////////////////////////////////////////////////////
130 - (void)messageQueue
:(MessageQueue
*)queue error
:(NSError
*)error
{
131 NSLog(@
"error = %@", error
);
134 - (void)messageQueueDidConnect
:(MessageQueue
*)queue
{
139 [_delegate debuggerEngineConnected
:self];
142 - (void)messageQueueDidDisconnect
:(MessageQueue
*)queue
{
144 [_dispatchTable removeAllObjects
];
145 [_delegate debuggerEngineDisconnected
:self];
148 // Callback for when a message has been sent.
149 - (void)messageQueue
:(MessageQueue
*)queue didSendMessage
:(NSString
*)message
{
150 NSInteger tag
= [self transactionIDFromCommand
:message
];
151 _lastWrittenID
= tag
;
153 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
154 LogEntry
* entry
= [LogEntry newSendEntry
:message
];
155 entry.lastReadTransactionID
= _lastReadID
;
156 entry.lastWrittenTransactionID
= _lastWrittenID
;
157 [logger recordEntry
:entry
];
160 // Callback with the message content when one has been receieved.
161 - (void)messageQueue
:(MessageQueue
*)queue didReceiveMessage
:(NSString
*)message
{
162 // Record this message in the transaction log.
163 LoggingController
* logger
= [[AppDelegate instance
] loggingController
];
164 LogEntry
* entry
= [LogEntry newReceiveEntry
:message
];
165 entry.lastReadTransactionID
= _lastReadID
;
166 entry.lastWrittenTransactionID
= _lastWrittenID
;
167 [logger recordEntry
:entry
];
169 // Parse the XML and test for errors.
170 NSError
* error
= nil;
171 NSXMLDocument
* xml
= [[NSXMLDocument alloc
] initWithXMLString
:message
172 options
:NSXMLDocumentTidyXML
175 [self messageQueue
:queue error
:error
];
178 NSInteger transactionID
= [self transactionIDFromResponse
:xml
];
180 _lastReadID
= transactionID
;
181 entry.lastReadTransactionID
= _lastReadID
;
183 if ([[[xml rootElement
] elementsForName
:@
"error"] count
] > 0) {
184 // Handle back-end errors.
185 [_delegate protocolClient
:self receivedErrorMessage
:xml
];
186 } else if ([[[xml rootElement
] name
] isEqualToString
:@
"init"]) {
187 // Handle the initial connection message.
188 [_delegate protocolClient
:self receivedInitialMessage
:xml
];
190 // Dispatch the handler for the message.
191 ProtocolClientMessageHandler handler
= [_dispatchTable objectForKey
:@
(transactionID
)];
193 NSLog(@
"Could not dispatch handler for transaction %ld: %@", transactionID
, message
);
197 [_dispatchTable removeObjectForKey
:@
(transactionID
)];