]> git.saurik.com Git - apple/security.git/blob - Network/xfercore.cpp
Security-30.1.tar.gz
[apple/security.git] / Network / xfercore.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 //
20 // xfercore - core data transfer engine
21 //
22 #include "xfercore.h"
23 #include <Security/debugging.h>
24
25
26 namespace Security {
27 namespace Network {
28
29
30 //
31 // Create an engine-level client object.
32 // @@@ Defer buffer allocation to mating?
33 // @@@ Defer state initialization to mating?
34 //
35 TransferEngine::Client::Client()
36 : mMode(invalidInput), mAutoCopyOut(false),
37 mSink(NULL), mSource(NULL),
38 mAutoFlush(true),
39 mReadBuffer(16384), mWriteBuffer(16384)
40 {
41 }
42
43 TransferEngine::Client::~Client()
44 {
45 }
46
47
48 //
49 // Add and remove clients to/from the engine
50 //
51 void TransferEngine::add(Client *client)
52 {
53 client->io = client->fileDesc(); // punch master I/O down to Selector client level
54 Selector::add(client->io, *client, input | critical); // initial registration
55 }
56
57 void TransferEngine::remove(Client *client)
58 {
59 #ifndef NDEBUG
60 if (!client->mReadBuffer.isEmpty())
61 debug("xferengine", "xfer %p(%d) HAD %ld BYTES READ LEFT",
62 client, client->fileDesc(), client->mReadBuffer.length());
63 if (!client->mWriteBuffer.isEmpty())
64 debug("xferengine", "xfer %p(%d) HAD %ld BYTES WRITE LEFT",
65 client, client->fileDesc(), client->mWriteBuffer.length());
66 #endif //NDEBUG
67 Selector::remove(client->io);
68 client->io = FileDesc(); // invalidate
69 }
70
71
72 //
73 // Mode switching.
74 // In addition to the generic switcher (mode), there are variants that set associated
75 // information, such as sources/sinks.
76 //
77 void TransferEngine::Client::mode(InputMode newMode)
78 {
79 debug("xferengine", "xfer %p(%d) switching to mode %d", this, fileDesc(), newMode);
80 switch (newMode) {
81 case rawInput:
82 case lineInput:
83 mMode = newMode;
84 break;
85 case connecting:
86 enable(output);
87 mMode = connecting;
88 break;
89 default:
90 assert(false); // can't switch to these modes like that
91 }
92 }
93
94 void TransferEngine::Client::mode(Sink &sink, size_t byteCount)
95 {
96 mMode = autoReadInput;
97 mSink = &sink;
98 mResidualReadCount = byteCount;
99 debug("xferengine", "xfer %p(%d) switching to autoReadInput (%ld bytes)",
100 this, fileDesc(), byteCount);
101 }
102
103 void TransferEngine::Client::mode(Source &source, size_t byteCount)
104 {
105 assert (!mAutoCopyOut); // no replacements, please
106 mAutoCopyOut = true;
107 mSource = &source;
108 mResidualWriteCount = byteCount;
109 debug("xferengine", "xfer %p(%d) enabling autoCopyOut mode (%ld bytes)",
110 this, fileDesc(), byteCount);
111 enable(output);
112 }
113
114
115 //
116 // Output methods. This queues output to be sent to the client's connection
117 // as soon as practical.
118 //
119 void TransferEngine::Client::printf(const char *format, ...)
120 {
121 va_list args;
122 va_start(args, format);
123 vprintf(format, args);
124 va_end(args);
125 }
126
127 void TransferEngine::Client::vprintf(const char *format, va_list args)
128 {
129 mWriteBuffer.vprintf(format, args);
130 #if !defined(NDEBUG)
131 char buffer[1024];
132 vsnprintf(buffer, sizeof(buffer), format, args);
133 debug("engineio", "%p(%d) <-- %s", this, fileDesc(), buffer);
134 #endif //NDEBUG
135 startOutput();
136 }
137
138 void TransferEngine::Client::printfe(const char *format, ...)
139 {
140 va_list args;
141 va_start(args, format);
142 vprintfe(format, args);
143 va_end(args);
144 }
145
146 void TransferEngine::Client::vprintfe(const char *format, va_list args)
147 {
148 mWriteBuffer.vprintf(format, args);
149 mWriteBuffer.printf("\r\n");
150 #if !defined(NDEBUG)
151 char buffer[1024];
152 vsnprintf(buffer, sizeof(buffer), format, args);
153 debug("engineio", "%p(%d) <-- %s[CRNL]", this, fileDesc(), buffer);
154 #endif //NDEBUG
155 startOutput();
156 }
157
158
159 //
160 // Set output auto-flush mode. Think of this as a weak output-hold mode.
161 // If autoflush is off, we don't try hard to send data out immediately. If it's
162 // on, we send data as soon as it's generated.
163 // Calling flushOutput(true) always generates I/O as needed to send output
164 // data NOW (even if the mode was already on).
165 //
166 void TransferEngine::Client::flushOutput(bool autoFlush)
167 {
168 mAutoFlush = autoFlush;
169 debug("engineio", "%p(%d) output flush %s", this, fileDesc(), autoFlush? "on" : "off");
170 if (mAutoFlush)
171 startOutput();
172 }
173
174
175 //
176 // StartOutput is called by output generators to get output flowing.
177 // It may generate output I/O, or hold things in buffers according to
178 // current settings.
179 //
180 void TransferEngine::Client::startOutput()
181 {
182 if (mAutoFlush) {
183 if (mAutoCopyOut && !mWriteBuffer.isFull())
184 autoCopy(); // try to tack on some autoCopy output
185 if (!mWriteBuffer.isEmpty()) {
186 mWriteBuffer.write(*this);
187 if (mAutoFlush || !mWriteBuffer.isEmpty()) { // possibly more output
188 enable(output); // ask for output-drain notification
189 } else {
190 disable(output); // no need for output-possible events
191 }
192 }
193 }
194 }
195
196
197 //
198 // Discard any data still in the input buffer.
199 // This is used to cope with unexpected garbage (protocol violations
200 // from the server), and shouldn't be used indiscriminately.
201 //
202 void TransferEngine::Client::flushInput()
203 {
204 if (!mReadBuffer.isEmpty()) {
205 debug("engineio", "flushing %ld bytes of input", mReadBuffer.length());
206 mReadBuffer.clear();
207 mInputFlushed = true; // inhibit normal buffer ops
208 }
209 }
210
211
212 //
213 // Given that autoCopyOut mode is active, try to transfer some bytes
214 // into the write buffer. This is a lazy, fast push, suitable for tacking on
215 // when you are about to send data for some other reason.
216 // Returns the number of bytes retrieved from the auto-Source (possibly zero).
217 //
218 size_t TransferEngine::Client::autoCopy()
219 {
220 size_t len = mWriteBuffer.available(); //@@@ (true) ?
221 if (mResidualWriteCount && mResidualWriteCount < len)
222 len = mResidualWriteCount;
223 void *addr; mWriteBuffer.locatePut(addr, len);
224 mSource->produce(addr, len);
225 debug("xferengine", "xfer %p(%d) autoCopyOut source delivered %ld bytes",
226 this, fileDesc(), len);
227 mWriteBuffer.usePut(len);
228 return len;
229 }
230
231
232 //
233 // This is the notify function called by the IP Selector layer when I/O is possible.
234 // It runs the state machines for all current clients, calling their transit methods
235 // in turn.
236 //
237 void TransferEngine::Client::notify(int fd, Type type)
238 {
239 try {
240 //@@@ Note: We do not currently do anything special about critical events.
241
242 if (type & Selector::output) {
243 // if we're in connecting mode
244 if (mMode == connecting) {
245 Socket s; s = fd; // Socket(fd) means something different...
246 int error = s.error();
247 debug("xferengine", "xfer %p(%d) connect (errno %d)",
248 this, fd, error);
249 transit(connectionDone, NULL, error);
250 return;
251 }
252
253 //@@@ use high/low water marks here
254 if (mAutoCopyOut && !mWriteBuffer.isFull()) {
255 if (autoCopy() == 0) {
256 switch (mSource->state()) {
257 case Source::stalled:
258 // ah well, maybe later
259 debug("xferengine", "xfer %p(%d) autoCopyOut source is stalled", this, fd);
260 break;
261 case Source::endOfData:
262 mAutoCopyOut = false; // done
263 debug("xferengine", "xfer %p(%d) autoCopyOut end of data", this, fd);
264 if (mResidualWriteCount > 0)
265 debug("xferengine", "xfer %p(%d) has %ld autoCopy bytes left",
266 this, fd, mResidualWriteCount);
267 transit(autoWriteDone);
268 if (!isActive())
269 return; // transit removed us; stop now
270 break;
271 default:
272 assert(false);
273 }
274 }
275 }
276 if (mWriteBuffer.isEmpty()) { // output possible, no output pending
277 debug("xferengine", "xfer %p(%d) disabling output (empty)", this, fd);
278 disable(output);
279 } else { // stuff some more
280 size_t length = mWriteBuffer.write(*this);
281 debug("xferengine", "xfer %p(%d) writing %ld bytes", this, fd, length);
282 }
283 }
284
285 if (type & Selector::input) {
286 IFDEBUG(debug("xferengine", "xfer %p(%d) input ready %d bytes",
287 this, fd, io.iocget<int>(FIONREAD)));
288
289 do {
290 mInputFlushed = false; // preset normal
291
292 //@@@ break out after partial buffer to give Equal Time to other transfers? good idea?!
293 if (!atEnd() && mReadBuffer.read(*this) == 0 && !atEnd()) {
294 mReadBuffer.read(*this, true);
295 }
296
297 if (mReadBuffer.isEmpty() && atEnd()) {
298 transit(endOfInput);
299 break;
300 }
301 switch (mMode) {
302 case rawInput:
303 rawInputTransit();
304 break;
305 case lineInput:
306 if (!lineInputTransit())
307 return; // no full line; try again later
308 break;
309 case autoReadInput:
310 autoReadInputTransit();
311 if (mMode != autoIODone)
312 break;
313 // autoRead completed; fall through to autoIODone handling
314 case autoIODone:
315 mMode = invalidInput; // pre-mark error
316 transit(autoReadDone); // notify; this must reset mode or exit
317 if (!isActive()) // if we're terminated...
318 return; // ... then go
319 assert(mMode != invalidInput); // else enforce mode reset
320 break;
321 case connecting:
322 {
323 // we should never be here. Selector gave us "read but not write" while connecting. FUBAR
324 Socket s; s = fd;
325 debug("xferengine",
326 "fd %d input while connecting (errno=%d, type=%d)",
327 fd, s.error(), type);
328 UnixError::throwMe(ECONNREFUSED); // likely interpretation
329 }
330 default:
331 debug("xferengine", "mode error in input sequencer (mode=%d)", mMode);
332 assert(false);
333 }
334 if (!io) // client has unhooked; clear buffer and exit loop
335 flushInput();
336 } while (!mReadBuffer.isEmpty());
337 //@@@ feed back for more output here? But also see comments above...
338 //@@@ probably better to take the trip through the Selector
339 }
340 } catch (CssmCommonError &err) {
341 transitError(err);
342 } catch (...) {
343 transitError(UnixError::make(EIO)); // best guess (could be anything)
344 }
345 }
346
347 void TransferEngine::Client::rawInputTransit()
348 {
349 // just shove it at the user
350 char *addr; size_t length = mReadBuffer.length();
351 mReadBuffer.locateGet(addr, length);
352 IFDEBUG(debug("engineio", "%p(%d) --> %d bytes RAW",
353 this, fileDesc(), io.iocget<int>(FIONREAD)));
354 transit(inputAvailable, addr, length);
355 if (!mInputFlushed)
356 mReadBuffer.useGet(length);
357 }
358
359 bool TransferEngine::Client::lineInputTransit()
360 {
361 char *line; size_t length = mReadBuffer.length();
362 mReadBuffer.locateGet(line, length);
363
364 char *nl;
365 for (nl = line; nl < line + length && *nl != '\n'; nl++) ;
366 if (nl == line + length) // no end-of-line, wait for more
367 return false;
368
369 if (nl > line && nl[-1] == '\r') { // proper \r\n termination
370 nl[-1] = '\0'; // terminate for transit convenience
371 debug("engineio", "%p(%d) --> %s", this, fileDesc(), line);
372 transit(inputAvailable, line, nl - line - 1);
373 } else { // improper, tolerate
374 nl[0] = '\0'; // terminate for transit convenience
375 debug("engineio", "%p(%d) [IMPROPER] --> %s", this, fileDesc(), line);
376 transit(inputAvailable, line, nl - line);
377 }
378 if (!mInputFlushed)
379 mReadBuffer.useGet(nl - line + 1);
380 return true;
381 }
382
383 void TransferEngine::Client::autoReadInputTransit()
384 {
385 debug("xferengine", "xfer %p(%d) %ld pending %d available",
386 this, fileDesc(), mReadBuffer.length(), io.iocget<int>(FIONREAD));
387 void *data; size_t length = mReadBuffer.length();
388 if (mResidualReadCount && mResidualReadCount < length)
389 length = mResidualReadCount;
390 mReadBuffer.locateGet(data, length);
391 debug("engineio", "%p(%d) --> %ld bytes autoReadInput", this, fileDesc(), length);
392 mSink->consume(data, length);
393 if (!mInputFlushed)
394 mReadBuffer.useGet(length);
395 if (mResidualReadCount && (mResidualReadCount -= length) == 0)
396 mMode = autoIODone;
397 }
398
399
400 //
401 // The (protected) tickle() method causes a one-time scan
402 // of the requesting client. This will simulate an input-ready event
403 // and possibly call the transit method.
404 // This is designed to be used from validate() or in other unusual
405 // external situations. Don't call this from within transit().
406 //
407 void TransferEngine::Client::tickle()
408 {
409 notify(io, input | critical);
410 }
411
412
413 //
414 // The default read/write methods perform direct I/O on the underlying file descriptor.
415 //
416 size_t TransferEngine::Client::read(void *data, size_t size)
417 { return io.read(data, size); }
418
419 size_t TransferEngine::Client::write(const void *data, size_t size)
420 { return io.write(data, size); }
421
422 bool TransferEngine::Client::atEnd() const
423 { return io.atEnd(); }
424
425
426 } // end namespace Network
427 } // end namespace Security