We need to use UTF8 string encoding instead of ASCII because we're receiving XML...
[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 // Read the first part of the response, the length of the packet.
170 char packetLength[8];
171 memset(&packetLength, 0x0, 8);
172 char c;
173 int i = 0;
174 while (recv(sock, &c, 1, 0) == 1 && c != 0x0)
175 packetLength[i++] = c;
176 int length = atoi(packetLength);
177
178 // Our final output.
179 NSMutableString* string = [[NSMutableString alloc] initWithCapacity:length];
180
181 // Create a buffer that we will move data from the network into.
182 char buffer[1024];
183
184 // The total amount of data we have currently read.
185 int received = 0;
186
187 // Loop until we have the entire packet.
188 while (received < length)
189 {
190 int size = recv(sock, &buffer, sizeof(buffer), 0);
191 if (size < 1)
192 {
193 [self error:@"Socket closed or could not be read"];
194 return nil;
195 }
196 NSString* temp = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
197 [string appendString:temp];
198 received += [temp length];
199 }
200
201 return [string autorelease];
202 }
203
204 /**
205 * Sends a given NSString over the socket. Returns YES on complete submission.
206 */
207 - (BOOL)send:(NSString*)data
208 {
209 data = [NSString stringWithFormat:@"%@\0", data];
210 int sent = send(sock, [data UTF8String], [data length], 0);
211 if (sent < 0)
212 {
213 [self error:@"Failed to write data to socket"];
214 return NO;
215 }
216 if (sent < [data length])
217 {
218 // TODO - do we really need to worry about partial sends with the lenght of our commands?
219 NSLog(@"FAIL: only partial packet was sent; sent %d bytes", sent);
220 return NO;
221 }
222
223 return YES;
224 }
225
226 /**
227 * Helper method that just calls -[DebuggerWindowController setError:] on the main thread
228 */
229 - (void)error:(NSString*)msg
230 {
231 [delegate performSelectorOnMainThread:@selector(setError:) withObject:msg waitUntilDone:NO];
232 }
233
234 @end