Fixing a bug in -[SocketWrapper receive] that could miss up to the last 8 bytes of...
[macgdbp.git] / Source / SocketWrapper.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2008, 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 "SocketWrapper.h"
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22 #include <unistd.h>
23
24 @interface SocketWrapper (Private)
25
26 - (void)error:(NSString *)msg;
27
28 @property (copy, readwrite, getter=remoteHost, setter=setHostname:) NSString *hostname;
29 @end
30
31 @implementation SocketWrapper
32
33 /**
34 * Initializes the socket wrapper with a host and port
35 */
36 - (id)initWithConnection:(DebuggerConnection *)cnx
37 {
38 if (self = [super init])
39 {
40 connection = [cnx retain];
41 port = [connection port];
42 }
43 return self;
44 }
45
46 /**
47 * Dealloc
48 */
49 - (void)dealloc
50 {
51 [connection release];
52 [hostname release];
53 [super dealloc];
54 }
55
56 /**
57 * Close our socket and clean up anything else
58 */
59 - (void)close
60 {
61 close(sock);
62 }
63
64 /**
65 * Returns the delegate
66 */
67 - (id)delegate
68 {
69 return delegate;
70 }
71
72 /**
73 * Sets the delegate but does *not* retain it
74 */
75 - (void)setDelegate:(id)aDelegate
76 {
77 delegate = aDelegate;
78 }
79
80 @synthesize hostname;
81
82 /**
83 * Connects to a socket on the port specified during init. This will dispatch another thread to do the
84 * actual waiting. Delegate notifications are posted along the way to let the client know what is going on.
85 */
86 - (void)connect
87 {
88 [NSThread detachNewThreadSelector:@selector(connect:) toTarget:self withObject:nil];
89 }
90
91 /**
92 * This does the actual dirty work (in a separate thread) of connecting to a socket
93 */
94 - (void)connect:(id)obj
95 {
96 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
97
98 // create an INET socket that we'll be listen()ing on
99 int socketOpen = socket(PF_INET, SOCK_STREAM, 0);
100
101 // create our address given the port
102 struct sockaddr_in address;
103 address.sin_family = AF_INET;
104 address.sin_port = htons(port);
105 address.sin_addr.s_addr = htonl(INADDR_ANY);
106 memset(address.sin_zero, '\0', sizeof(address.sin_zero));
107
108 // allow an already-opened socket to be reused
109 int yes = 1;
110 setsockopt(socketOpen, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
111
112 // bind the socket... and don't give up until we've tried for a while
113 int tries = 0;
114 while (bind(socketOpen, (struct sockaddr *)&address, sizeof(address)) < 0)
115 {
116 if (tries >= 5)
117 {
118 close(socketOpen);
119 [self error:@"Could not bind to socket"];
120 [pool release];
121 return;
122 }
123 NSLog(@"couldn't bind to the socket... trying again in 5");
124 sleep(5);
125 tries++;
126 }
127
128 // now we just have to keep our ears open
129 if (listen(socketOpen, 0) == -1)
130 {
131 [self error:@"Could not use bound socket for listening"];
132 }
133
134 // accept a connection
135 struct sockaddr_in remoteAddress;
136 socklen_t remoteAddressLen = sizeof(remoteAddress);
137 sock = accept(socketOpen, (struct sockaddr *)&remoteAddress, &remoteAddressLen);
138 if (sock < 0)
139 {
140 close(socketOpen);
141 [self error:@"Client failed to accept remote socket"];
142 [pool release];
143 return;
144 }
145
146 // we're done listening now that we have a connection
147 close(socketOpen);
148
149 struct sockaddr_in addr;
150 socklen_t addrLength;
151 if (getpeername(sock, (struct sockaddr *)&addr, &addrLength) < 0)
152 {
153 [self error:@"Could not get remote hostname."];
154 }
155 char *name = inet_ntoa(addr.sin_addr);
156 [self setHostname:[NSString stringWithUTF8String:name]];
157
158 [connection performSelectorOnMainThread:@selector(socketDidAccept:) withObject:nil waitUntilDone:NO];
159
160 [pool release];
161 }
162
163 /**
164 * Reads from the socket and returns the result as a NSString (because it's always going to be XML). Be aware
165 * that the underlying socket recv() call will *wait* for the server to send a message, so be sure that this
166 * is used either in a threaded environment so the interface does not hang, or when you *know* the server
167 * will return something (which we almost always do). Returns the data that was received from the socket.
168 */
169 - (NSString *)receive
170 {
171 // create a buffer
172 char buffer[1024];
173
174 // do our initial recv() call to get (hopefully) all the data and the lengh of the packet
175 int recvd = recv(sock, &buffer, sizeof(buffer), 0);
176
177 // take the received data and put it into an NSData
178 NSMutableString *str = [NSMutableString string];
179
180 // strip the length from the packet, and clear the null byte then add it to the NSData
181 char packetLength[8];
182 memset(packetLength, '\0', sizeof(packetLength));
183 int i = 0;
184 while (buffer[i] != '\0')
185 {
186 packetLength[i] = buffer[i];
187 i++;
188 }
189
190 // we also want the null byte, so move us up 1
191 i++;
192
193 // the total length of the full transmission
194 int length = atoi(packetLength);
195
196 // move the packet part of the received data into it's own char[]
197 char packet[sizeof(buffer)];
198 memset(packet, '\0', sizeof(packet));
199 memmove(packet, &buffer[i], recvd - i);
200
201 // convert bytes to NSString
202 [str appendString:[NSString stringWithCString:packet length:recvd - i]];
203
204 // check if we have a partial packet
205 if (length + i > sizeof(buffer))
206 {
207 while (recvd < length + i)
208 {
209 int latest = recv(sock, &buffer, sizeof(buffer), 0);
210 if (latest < 1)
211 {
212 [self error:@"Socket closed or could not be read"];
213 return nil;
214 }
215 [str appendString:[NSString stringWithCString:buffer length:latest]];
216 recvd += latest;
217 }
218 }
219
220 NSString *tmp = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // strip whitespace
221 tmp = [tmp substringToIndex:[tmp length] - 1]; // don't want the null byte
222
223 NSAssert([tmp UTF8String][[tmp length] - 1] == '>', @"-[SocketWrapper receive] buffer is incomplete");
224
225 return tmp;
226 }
227
228 /**
229 * Sends a given NSString over the socket. Returns YES on complete submission.
230 */
231 - (BOOL)send:(NSString *)data
232 {
233 data = [NSString stringWithFormat:@"%@\0", data];
234 int sent = send(sock, [data UTF8String], [data length], 0);
235 if (sent < 0)
236 {
237 [self error:@"Failed to write data to socket"];
238 return NO;
239 }
240 if (sent < [data length])
241 {
242 // TODO - do we really need to worry about partial sends with the lenght of our commands?
243 NSLog(@"FAIL: only partial packet was sent; sent %d bytes", sent);
244 return NO;
245 }
246
247 return YES;
248 }
249
250 /**
251 * Helper method that just calls -[DebuggerWindowController setError:] on the main thread
252 */
253 - (void)error:(NSString *)msg
254 {
255 [delegate performSelectorOnMainThread:@selector(errorEncountered:) withObject:msg waitUntilDone:NO];
256 }
257
258 @end