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