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