Bump project version to 212.1.
[macgdbp.git] / Source / ProtocolClient.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2013, Blue Static <http://www.bluestatic.org>
4 *
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.
8 *
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.
12 *
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
15 */
16
17 #import "ProtocolClient.h"
18
19 #import "AppDelegate.h"
20 #import "LoggingController.h"
21
22 @implementation ProtocolClient {
23 // The object responsible for the actual communication with the debug server.
24 MessageQueue* _messageQueue;
25
26 // The delegate of this class, which receives high-level messages about the
27 // state of the debugger.
28 id<ProtocolClientDelegate> _delegate; // weak
29
30 // A map between transaction ID and handler block for that message.
31 NSMutableDictionary<NSNumber*, ProtocolClientMessageHandler>* _dispatchTable;
32
33 // The next transaction ID to assign.
34 int _nextID;
35
36 // Records the last read and written transaction IDs. These are only used in
37 // creating LogEntry objects.
38 NSInteger _lastReadID;
39 NSInteger _lastWrittenID;
40 }
41
42 - (id)initWithDelegate:(id<ProtocolClientDelegate>)delegate {
43 if ((self = [super init])) {
44 _delegate = delegate;
45 _dispatchTable = [[NSMutableDictionary alloc] init];
46 }
47 return self;
48 }
49
50 - (BOOL)isConnected {
51 return [_messageQueue isConnected];
52 }
53
54 - (void)connectOnPort:(NSUInteger)port {
55 assert(!_messageQueue);
56 _messageQueue = [[MessageQueue alloc] initWithPort:port delegate:self];
57 [_messageQueue connect];
58 }
59
60 - (void)disconnect {
61 [_messageQueue disconnect];
62 }
63
64 - (void)sendCommandWithFormat:(NSString*)format, ... {
65 // Collect varargs and format command.
66 va_list args;
67 va_start(args, format);
68 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
69 va_end(args);
70
71 [self sendCommandWithFormat:command handler:^(NSXMLDocument* message){}];
72 }
73
74 - (void)sendCommandWithFormat:(NSString*)format
75 handler:(ProtocolClientMessageHandler)handler, ... {
76 // Collect varargs and format command.
77 va_list args;
78 va_start(args, handler);
79 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
80 va_end(args);
81
82 int transaction = _nextID++;
83 NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %d", command, transaction];
84
85 assert(_messageQueue);
86 [_dispatchTable setObject:[handler copy] forKey:@(transaction)];
87 [_messageQueue sendMessage:taggedCommand];
88 }
89
90 - (void)sendCustomCommandWithFormat:(NSString*)format
91 handler:(ProtocolClientMessageHandler)handler, ... {
92 // Collect varargs and format command.
93 va_list args;
94 va_start(args, handler);
95 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
96 va_end(args);
97
98 int transaction = _nextID++;
99 NSString* taggedCommand =
100 [command stringByReplacingOccurrencesOfString:@"{txn}"
101 withString:[NSString stringWithFormat:@"%d", transaction]];
102
103 assert(_messageQueue);
104 [_dispatchTable setObject:[handler copy] forKey:@(transaction)];
105 [_messageQueue sendMessage:taggedCommand];
106 }
107
108
109 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response {
110 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
111 }
112
113 - (NSInteger)transactionIDFromCommand:(NSString*)command {
114 NSRange occurrence = [command rangeOfString:@"-i "];
115 if (occurrence.location == NSNotFound)
116 return NSNotFound;
117 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
118 return [transaction intValue];
119 }
120
121 + (NSString*)escapedFilePathURI:(NSString*)path {
122 // The backend will interpret this custom scheme.
123 if ([path hasPrefix:@"gdbp://"])
124 return path;
125 return [[NSURL fileURLWithPath:path] absoluteString];
126 }
127
128 // MessageQueueDelegate ////////////////////////////////////////////////////////
129
130 - (void)messageQueue:(MessageQueue*)queue error:(NSError*)error {
131 NSLog(@"error = %@", error);
132 }
133
134 - (void)messageQueueDidConnect:(MessageQueue*)queue {
135 _nextID = 0;
136 _lastReadID = 0;
137 _lastWrittenID = 0;
138
139 [_delegate debuggerEngineConnected:self];
140 }
141
142 - (void)messageQueueDidDisconnect:(MessageQueue*)queue {
143 _messageQueue = nil;
144 [_dispatchTable removeAllObjects];
145 [_delegate debuggerEngineDisconnected:self];
146 }
147
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;
152
153 LoggingController* logger = [[AppDelegate instance] loggingController];
154 LogEntry* entry = [LogEntry newSendEntry:message];
155 entry.lastReadTransactionID = _lastReadID;
156 entry.lastWrittenTransactionID = _lastWrittenID;
157 [logger recordEntry:entry];
158 }
159
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];
168
169 // Parse the XML and test for errors.
170 NSError* error = nil;
171 NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message
172 options:NSXMLDocumentTidyXML
173 error:&error];
174 if (error) {
175 [self messageQueue:queue error:error];
176 return;
177 }
178 NSInteger transactionID = [self transactionIDFromResponse:xml];
179
180 _lastReadID = transactionID;
181 entry.lastReadTransactionID = _lastReadID;
182
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];
189 } else {
190 // Dispatch the handler for the message.
191 ProtocolClientMessageHandler handler = [_dispatchTable objectForKey:@(transactionID)];
192 if (!handler) {
193 NSLog(@"Could not dispatch handler for transaction %ld: %@", transactionID, message);
194 return;
195 }
196 handler(xml);
197 [_dispatchTable removeObjectForKey:@(transactionID)];
198 }
199 }
200
201 @end