Write ProtocolClient, the layer that talks XML on top of a MessageQueue.
[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 @interface ProtocolClient (Private)
20 - (void)postReceivedMessage:(NSXMLDocument*)message;
21 @end
22
23 @implementation ProtocolClient
24
25 - (id)initWithDelegate:(NSObject<ProtocolClientDelegate>*)delegate {
26 if ((self = [super init])) {
27 _delegate = delegate;
28 _delegateThread = [NSThread currentThread];
29 _lock = [[NSRecursiveLock alloc] init];
30 }
31 return self;
32 }
33
34 - (void)dealloc {
35 [_lock release];
36 [super dealloc];
37 }
38
39 - (BOOL)isConnected {
40 return [_messageQueue isConnected];
41 }
42
43 - (void)connectOnPort:(NSUInteger)port {
44 assert(!_messageQueue);
45 _messageQueue = [[MessageQueue alloc] initWithPort:port delegate:self];
46 [_messageQueue connect];
47 }
48
49 - (void)disconnect {
50 [_messageQueue disconnect];
51 }
52
53 - (NSNumber*)sendCommandWithFormat:(NSString*)format, ... {
54 // Collect varargs and format command.
55 va_list args;
56 va_start(args, format);
57 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
58 va_end(args);
59
60 NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++];
61 NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey];
62
63 [_messageQueue sendMessage:taggedCommand];
64 return callbackKey;
65 }
66
67 - (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ... {
68 // Collect varargs and format command.
69 va_list args;
70 va_start(args, format);
71 NSString* command = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
72 va_end(args);
73
74 NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++];
75 NSString* taggedCommand = [command stringByReplacingOccurrencesOfString:@"{txn}"
76 withString:[callbackKey stringValue]];
77
78 [_messageQueue sendMessage:taggedCommand];
79 return callbackKey;
80 }
81
82 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response {
83 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
84 }
85
86 - (NSInteger)transactionIDFromCommand:(NSString*)command {
87 NSRange occurrence = [command rangeOfString:@"-i "];
88 if (occurrence.location == NSNotFound)
89 return NSNotFound;
90 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
91 return [transaction intValue];
92 }
93
94 // MessageQueueDelegate ////////////////////////////////////////////////////////
95
96 - (void)messageQueueError:(NSError*)error {
97 NSLog(@"error = %@", error);
98 }
99
100 - (void)clientDidConnect:(MessageQueue*)queue {
101 [_lock lock];
102 _nextID = 0;
103 _lastReadID = 0;
104 _lastWrittenID = 0;
105 [_lock unlock];
106
107 [_delegate performSelector:@selector(debuggerEngineConnected:)
108 onThread:_delegateThread
109 withObject:self
110 waitUntilDone:NO];
111 }
112
113 - (void)clientDidDisconnect:(MessageQueue*)queue {
114 [_delegate performSelector:@selector(debuggerEngineDisconnected:)
115 onThread:_delegateThread
116 withObject:self
117 waitUntilDone:NO];
118 }
119
120 // If the write stream is ready, the delegate controls whether or not the next
121 // pending message should be sent via the result of this method.
122 - (BOOL)shouldSendMessage {
123 [_lock lock];
124 BOOL r = _lastReadID >= _lastWrittenID;
125 [_lock unlock];
126 return r;
127 }
128
129 // Callback for when a message has been sent.
130 - (void)didSendMessage:(NSString*)message {
131 NSInteger tag = [self transactionIDFromCommand:message];
132 [_lock lock];
133 _lastWrittenID = tag;
134 [_lock unlock];
135 }
136
137 // Callback with the message content when one has been receieved.
138 - (void)didReceiveMessage:(NSString*)message {
139 // Test if we can convert it into an NSXMLDocument.
140 NSError* error = nil;
141 NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message
142 options:NSXMLDocumentTidyXML
143 error:&error];
144 if (error) {
145 [self messageQueueError:error];
146 return;
147 }
148
149 // Validate the transaction.
150 NSInteger transaction = [self transactionIDFromResponse:xml];
151 if (transaction < _lastReadID) {
152 NSLog(@"Transaction #%d is out of date (lastRead = %d). Dropping packet: %@",
153 transaction, _lastReadID, message);
154 return;
155 }
156 if (transaction != _lastWrittenID) {
157 NSLog(@"Transaction #%d received out of order. lastRead = %d, lastWritten = %d. Continuing.",
158 transaction, _lastReadID, _lastWrittenID);
159 }
160
161 _lastReadID = transaction;
162
163 [self performSelector:@selector(postReceivedMessage:)
164 onThread:_delegateThread
165 withObject:xml
166 waitUntilDone:NO];
167 }
168
169 // Private /////////////////////////////////////////////////////////////////////
170
171 - (void)postReceivedMessage:(NSXMLDocument*)message {
172 [_delegate debuggerEngine:self receivedMessage:message];
173 }
174
175 @end