Finally settled on a delegate/notification system combination that works, and is...
[macgdbp.git] / Source / SocketWrapper.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2002 - 2007, 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 NSString *sockNotificationDebuggerConnection = @"debuggerconnection";
25 NSString *sockDidAcceptNotification = @"socketdidaccept";
26
27 @implementation SocketWrapper
28
29 /**
30 * Initializes the socket wrapper with a host and port
31 */
32 - (id)initWithPort: (int)port
33 {
34 if (self = [super init])
35 {
36 _port = port;
37
38 // the delegate notifications work funky because of threads. we register ourselves as the
39 // observer and then pass up the messages that are actually from this object (as we can't only observe self due to threads)
40 // to our delegate, and not to all delegates
41 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_sendMessageToDelegate:) name: nil object: nil];
42 }
43 return self;
44 }
45
46 /**
47 * Close our socket and clean up anything else
48 */
49 - (void)dealloc
50 {
51 close(_socket);
52
53 [super dealloc];
54 }
55
56 /**
57 * Returns the delegate
58 */
59 - (id)delegate
60 {
61 return _delegate;
62 }
63
64 /**
65 * Sets the delegate but does *not* retain it
66 */
67 - (void)setDelegate: (id)delegate
68 {
69 _delegate = delegate;
70 }
71
72 /**
73 * This is the notification listener for all types of notifications. If the notifications are from a SocketWrapper
74 * class, it checks that the value of _delegate in the NSNotification's userInfo matches that of this object. If it does,
75 * then the notification was sent from the same object in another thread and it passes the message along to the object's
76 * delegate. Complicated enough?
77 */
78 - (void)_sendMessageToDelegate: (NSNotification *)notif
79 {
80 // this isn't us, so there's no point in continuing
81 if ([[notif userInfo] objectForKey: sockNotificationDebuggerConnection] != _delegate)
82 {
83 return;
84 }
85
86 NSString *name = [notif name];
87
88 if (name == sockDidAcceptNotification)
89 {
90 [_delegate socketDidAccept];
91 }
92 }
93
94 /**
95 * Connects to a socket on the port specified during init. This will dispatch another thread to do the
96 * actual waiting. Delegate notifications are posted along the way to let the client know what is going on.
97 */
98 - (void)connect
99 {
100 [NSThread detachNewThreadSelector: @selector(_connect:) toTarget: self withObject: nil];
101 }
102
103 /**
104 * This does the actual dirty work (in a separate thread) of connecting to a socket
105 */
106 - (void)_connect: (id)obj
107 {
108 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
109
110 // create an INET socket that we'll be listen()ing on
111 int socketOpen = socket(PF_INET, SOCK_STREAM, 0);
112
113 // create our address given the port
114 struct sockaddr_in address;
115 address.sin_family = AF_INET;
116 address.sin_port = htons(_port);
117 address.sin_addr.s_addr = htonl(INADDR_ANY);
118 memset(address.sin_zero, '\0', sizeof(address.sin_zero));
119
120 // bind the socket... and don't give up until we've tried for a while
121 int tries = 0;
122 while (bind(socketOpen, (struct sockaddr *)&address, sizeof(address)) < 0)
123 {
124 if (tries >= 5)
125 {
126 close(socketOpen);
127 [_delegate errorEncountered: nil];
128 }
129 NSLog(@"couldn't bind to the socket... trying again in 5");
130 sleep(5);
131 tries++;
132 }
133
134 // now we just have to keep our ears open
135 if (listen(socketOpen, 0) == -1)
136 {
137 [_delegate errorEncountered: nil];
138 }
139
140 // accept a connection
141 struct sockaddr_in remoteAddress;
142 socklen_t remoteAddressLen = sizeof(remoteAddress);
143 _socket = accept(socketOpen, (struct sockaddr *)&remoteAddress, &remoteAddressLen);
144 if (_socket < 0)
145 {
146 close(socketOpen);
147 [_delegate errorEncountered: nil];
148 }
149
150 // we're done listening now that we have a connection
151 close(socketOpen);
152
153 [self _postNotification: sockDidAcceptNotification withObject: nil];
154
155 [pool release];
156 }
157
158 /**
159 * Reads from the socket and returns the result as a NSString (because it's always going to be XML). Be aware
160 * that the underlying socket recv() call will *wait* for the server to send a message, so be sure that this
161 * is used either in a threaded environment so the interface does not hang, or when you *know* the server
162 * will return something (which we almost always do).
163 *
164 * Data string returned is autorelease'd
165 */
166 - (void)receive
167 {
168 // create a buffer
169 char buffer[1024];
170
171 // do our initial recv() call to get (hopefully) all the data and the lengh of the packet
172 int recvd = recv(_socket, &buffer, sizeof(buffer), 0);
173
174 // take the received data and put it into an NSData
175 NSMutableData *data = [NSMutableData data];
176
177 // strip the length from the packet, and clear the null byte then add it to the NSData
178 char packetLength[8];
179 int i = 0;
180 while (buffer[i] != '\0')
181 {
182 packetLength[i] = buffer[i];
183 i++;
184 }
185
186 // we also want the null byte, so move us up 1
187 i++;
188
189 // the total length of the full transmission
190 int length = atoi(packetLength);
191
192 // move the packet part of the received data into it's own char[]
193 char packet[sizeof(buffer)];
194 memmove(packet, &buffer[i], recvd - i);
195
196 // convert bytes to NSData
197 [data appendBytes: packet length: recvd];
198
199 // check if we have a partial packet
200 if (length + i > sizeof(buffer))
201 {
202 while (recvd < length)
203 {
204 int latest = recv(_socket, &buffer, sizeof(buffer), 0);
205 if (latest < 1)
206 {
207 NSLog(@"socket closed or error");
208 }
209 [data appendBytes: buffer length: latest];
210 recvd += latest;
211 }
212 }
213
214 // convert the NSData into a NSString
215 [_delegate dataReceived: [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease]];
216 }
217
218 /**
219 * Sends a given NSString over the socket
220 */
221 - (void)send: (NSString *)data
222 {
223 data = [NSString stringWithFormat: @"%@\0", data];
224 int sent = send(_socket, [data UTF8String], [data length], 0);
225 if (sent < 0)
226 {
227 NSLog(@"error in sending");
228 }
229 if (sent < [data length])
230 {
231 // TODO - do we really need to worry about partial sends with the lenght of our commands?
232 NSLog(@"FAIL: only partial packet was sent; sent %d bytes", sent);
233 }
234
235 [_delegate dataSent];
236 }
237
238 /**
239 * Helper method to simply post a notification to the default notification center with a given name and object
240 */
241 - (void)_postNotification: (NSString *)name withObject: (id)obj
242 {
243 NSDictionary *dict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: _delegate, nil]
244 forKeys: [NSArray arrayWithObjects: sockNotificationDebuggerConnection, nil]];
245 [[NSNotificationCenter defaultCenter] postNotificationName: name object: obj userInfo: dict];
246 }
247
248 @end