Prevent the Preferences window from jumping around when it opens by disabling
[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 @synthesize delegate;
33
34 /**
35 * Initializes the socket wrapper with a port
36 */
37 - (id)initWithPort:(int)aPort;
38 {
39 if (self = [super init])
40 {
41 port = aPort;
42 }
43 return self;
44 }
45
46 /**
47 * Dealloc
48 */
49 - (void)dealloc
50 {
51 [hostname release];
52 [super dealloc];
53 }
54
55 /**
56 * Close our socket and clean up anything else
57 */
58 - (void)close
59 {
60 close(sock);
61 }
62
63 /**
64 * Connects to a socket on the port specified during init. This will dispatch another thread to do the
65 * actual waiting. Delegate notifications are posted along the way to let the client know what is going on.
66 */
67 - (void)connect
68 {
69 [NSThread detachNewThreadSelector:@selector(connect:) toTarget:self withObject:nil];
70 }
71
72 /**
73 * This does the actual dirty work (in a separate thread) of connecting to a socket
74 */
75 - (void)connect:(id)obj
76 {
77 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
78
79 // create an INET socket that we'll be listen()ing on
80 int socketOpen = socket(PF_INET, SOCK_STREAM, 0);
81
82 // create our address given the port
83 struct sockaddr_in address;
84 address.sin_family = AF_INET;
85 address.sin_port = htons(port);
86 address.sin_addr.s_addr = htonl(INADDR_ANY);
87 memset(address.sin_zero, '\0', sizeof(address.sin_zero));
88
89 // allow an already-opened socket to be reused
90 int yes = 1;
91 setsockopt(socketOpen, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
92
93 // bind the socket... and don't give up until we've tried for a while
94 int tries = 0;
95 while (bind(socketOpen, (struct sockaddr*)&address, sizeof(address)) < 0)
96 {
97 if (tries >= 5)
98 {
99 close(socketOpen);
100 [self error:@"Could not bind to socket"];
101 [pool release];
102 return;
103 }
104 NSLog(@"couldn't bind to the socket... trying again in 5");
105 sleep(5);
106 tries++;
107 }
108
109 // now we just have to keep our ears open
110 if (listen(socketOpen, 0) == -1)
111 {
112 [self error:@"Could not use bound socket for listening"];
113 }
114
115 // accept a connection
116 struct sockaddr_in remoteAddress;
117 socklen_t remoteAddressLen = sizeof(remoteAddress);
118 sock = accept(socketOpen, (struct sockaddr*)&remoteAddress, &remoteAddressLen);
119 if (sock < 0)
120 {
121 close(socketOpen);
122 [self error:@"Client failed to accept remote socket"];
123 [pool release];
124 return;
125 }
126
127 // we're done listening now that we have a connection
128 close(socketOpen);
129
130 struct sockaddr_in addr;
131 socklen_t addrLength;
132 if (getpeername(sock, (struct sockaddr*)&addr, &addrLength) < 0)
133 {
134 [self error:@"Could not get remote hostname."];
135 }
136 char* name = inet_ntoa(addr.sin_addr);
137 [self setHostname:[NSString stringWithUTF8String:name]];
138
139 [delegate socketDidAccept];
140
141 [pool release];
142 }
143
144 /**
145 * Reads from the socket and returns the result as a NSString (because it's always going to be XML). Be aware
146 * that the underlying socket recv() call will *wait* for the server to send a message, so be sure that this
147 * is used either in a threaded environment so the interface does not hang, or when you *know* the server
148 * will return something (which we almost always do). Returns the data that was received from the socket.
149 */
150 - (NSString*)receive
151 {
152 // Read the first part of the response, the length of the packet.
153 char packetLength[8];
154 memset(&packetLength, 0x0, 8);
155 char c;
156 int i = 0;
157 while (recv(sock, &c, 1, 0) == 1 && c != 0x0)
158 packetLength[i++] = c;
159 int length = atoi(packetLength);
160
161 // Our final output.
162 NSMutableString* string = [[NSMutableString alloc] initWithCapacity:length];
163
164 // Create a buffer that we will move data from the network into.
165 char buffer[1024];
166
167 // The total amount of data we have currently read.
168 int received = 0;
169
170 // Loop until we have the entire packet.
171 while (received < length)
172 {
173 int size = recv(sock, &buffer, sizeof(buffer), 0);
174 if (size < 1)
175 {
176 [self error:@"Socket closed or could not be read"];
177 return nil;
178 }
179 NSString* temp = [NSString stringWithUTF8String:buffer];
180 [string appendString:temp];
181 received += [temp length];
182 }
183
184 return [string autorelease];
185 }
186
187 /**
188 * Sends a given NSString over the socket. Returns YES on complete submission.
189 */
190 - (BOOL)send:(NSString*)data
191 {
192 data = [NSString stringWithFormat:@"%@\0", data];
193 int sent = send(sock, [data UTF8String], [data length], 0);
194 if (sent < 0)
195 {
196 [self error:@"Failed to write data to socket"];
197 return NO;
198 }
199 if (sent < [data length])
200 {
201 // TODO - do we really need to worry about partial sends with the lenght of our commands?
202 NSLog(@"FAIL: only partial packet was sent; sent %d bytes", sent);
203 return NO;
204 }
205
206 return YES;
207 }
208
209 /**
210 * Helper method that just calls |-errorEncountered:|
211 */
212 - (void)error:(NSString*)msg
213 {
214 [delegate errorEncountered:msg];
215 }
216
217 @end