1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set sw=2 ts=8 et tw=80 : */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Patrick McManus <mcmanus@ducksong.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsHttp.h"
41 : #include "SpdySession.h"
42 : #include "SpdyStream.h"
43 : #include "nsAlgorithm.h"
44 : #include "prnetdb.h"
45 : #include "nsHttpRequestHead.h"
46 : #include "mozilla/Telemetry.h"
47 : #include "nsISocketTransport.h"
48 : #include "nsISupportsPriority.h"
49 :
50 : #ifdef DEBUG
51 : // defined by the socket transport service while active
52 : extern PRThread *gSocketThread;
53 : #endif
54 :
55 : namespace mozilla {
56 : namespace net {
57 :
58 0 : SpdyStream::SpdyStream(nsAHttpTransaction *httpTransaction,
59 : SpdySession *spdySession,
60 : nsISocketTransport *socketTransport,
61 : PRUint32 chunkSize,
62 : z_stream *compressionContext,
63 : PRInt32 priority)
64 : : mUpstreamState(GENERATING_SYN_STREAM),
65 : mTransaction(httpTransaction),
66 : mSession(spdySession),
67 : mSocketTransport(socketTransport),
68 : mSegmentReader(nsnull),
69 : mSegmentWriter(nsnull),
70 : mStreamID(0),
71 : mChunkSize(chunkSize),
72 : mSynFrameComplete(0),
73 : mRequestBlockedOnRead(0),
74 : mSentFinOnData(0),
75 : mRecvdFin(0),
76 : mFullyOpen(0),
77 : mSentWaitingFor(0),
78 : mTxInlineFrameSize(SpdySession::kDefaultBufferSize),
79 : mTxInlineFrameUsed(0),
80 : mTxStreamFrameSize(0),
81 : mZlib(compressionContext),
82 : mRequestBodyLenRemaining(0),
83 : mPriority(priority),
84 : mTotalSent(0),
85 0 : mTotalRead(0)
86 : {
87 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
88 :
89 0 : LOG3(("SpdyStream::SpdyStream %p", this));
90 :
91 0 : mTxInlineFrame = new char[mTxInlineFrameSize];
92 0 : }
93 :
94 0 : SpdyStream::~SpdyStream()
95 : {
96 0 : }
97 :
98 : // ReadSegments() is used to write data down the socket. Generally, HTTP
99 : // request data is pulled from the approriate transaction and
100 : // converted to SPDY data. Sometimes control data like a window-update is
101 : // generated instead.
102 :
103 : nsresult
104 0 : SpdyStream::ReadSegments(nsAHttpSegmentReader *reader,
105 : PRUint32 count,
106 : PRUint32 *countRead)
107 : {
108 0 : LOG3(("SpdyStream %p ReadSegments reader=%p count=%d state=%x",
109 : this, reader, count, mUpstreamState));
110 :
111 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
112 :
113 0 : nsresult rv = NS_ERROR_UNEXPECTED;
114 0 : mRequestBlockedOnRead = 0;
115 :
116 0 : switch (mUpstreamState) {
117 : case GENERATING_SYN_STREAM:
118 : case GENERATING_REQUEST_BODY:
119 : case SENDING_REQUEST_BODY:
120 : // Call into the HTTP Transaction to generate the HTTP request
121 : // stream. That stream will show up in OnReadSegment().
122 0 : mSegmentReader = reader;
123 0 : rv = mTransaction->ReadSegments(this, count, countRead);
124 0 : mSegmentReader = nsnull;
125 :
126 : // Check to see if the transaction's request could be written out now.
127 : // If not, mark the stream for callback when writing can proceed.
128 0 : if (NS_SUCCEEDED(rv) &&
129 : mUpstreamState == GENERATING_SYN_STREAM &&
130 0 : !mSynFrameComplete)
131 0 : mSession->TransactionHasDataToWrite(this);
132 :
133 : // mTxinlineFrameUsed represents any queued un-sent frame. It might
134 : // be 0 if there is no such frame, which is not a gurantee that we
135 : // don't have more request body to send - just that any data that was
136 : // sent comprised a complete SPDY frame. Likewise, a non 0 value is
137 : // a queued, but complete, spdy frame length.
138 :
139 : // Mark that we are blocked on read if the http transaction needs to
140 : // provide more of the request message body and there is nothing queued
141 : // for writing
142 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
143 0 : mRequestBlockedOnRead = 1;
144 :
145 0 : if (!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
146 0 : LOG3(("ReadSegments %p: Sending request data complete, mUpstreamState=%x",
147 : this, mUpstreamState));
148 0 : if (mSentFinOnData) {
149 0 : ChangeState(UPSTREAM_COMPLETE);
150 : }
151 : else {
152 0 : GenerateDataFrameHeader(0, true);
153 0 : ChangeState(SENDING_FIN_STREAM);
154 0 : mSession->TransactionHasDataToWrite(this);
155 0 : rv = NS_BASE_STREAM_WOULD_BLOCK;
156 : }
157 : }
158 :
159 0 : break;
160 :
161 : case SENDING_SYN_STREAM:
162 : // We were trying to send the SYN-STREAM but were blocked from trying
163 : // to transmit it the first time(s).
164 0 : mSegmentReader = reader;
165 0 : rv = TransmitFrame(nsnull, nsnull);
166 0 : mSegmentReader = nsnull;
167 0 : *countRead = 0;
168 0 : if (NS_SUCCEEDED(rv)) {
169 0 : NS_ABORT_IF_FALSE(!mTxInlineFrameUsed,
170 : "Transmit Frame should be all or nothing");
171 :
172 0 : if (mSentFinOnData) {
173 0 : ChangeState(UPSTREAM_COMPLETE);
174 0 : rv = NS_OK;
175 : }
176 : else {
177 0 : rv = NS_BASE_STREAM_WOULD_BLOCK;
178 0 : ChangeState(GENERATING_REQUEST_BODY);
179 0 : mSession->TransactionHasDataToWrite(this);
180 : }
181 : }
182 0 : break;
183 :
184 : case SENDING_FIN_STREAM:
185 : // We were trying to send the FIN-STREAM but were blocked from
186 : // sending it out - try again.
187 0 : if (!mSentFinOnData) {
188 0 : mSegmentReader = reader;
189 0 : rv = TransmitFrame(nsnull, nsnull);
190 0 : mSegmentReader = nsnull;
191 0 : NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
192 : "Transmit Frame should be all or nothing");
193 0 : if (NS_SUCCEEDED(rv))
194 0 : ChangeState(UPSTREAM_COMPLETE);
195 : }
196 : else {
197 0 : rv = NS_OK;
198 0 : mTxInlineFrameUsed = 0; // cancel fin data packet
199 0 : ChangeState(UPSTREAM_COMPLETE);
200 : }
201 :
202 0 : *countRead = 0;
203 :
204 : // don't change OK to WOULD BLOCK. we are really done sending if OK
205 0 : break;
206 :
207 : case UPSTREAM_COMPLETE:
208 0 : *countRead = 0;
209 0 : rv = NS_OK;
210 0 : break;
211 :
212 : default:
213 0 : NS_ABORT_IF_FALSE(false, "SpdyStream::ReadSegments unknown state");
214 0 : break;
215 : }
216 :
217 0 : return rv;
218 : }
219 :
220 : // WriteSegments() is used to read data off the socket. Generally this is
221 : // just the SPDY frame header and from there the appropriate SPDYStream
222 : // is identified from the Stream-ID. The http transaction associated with
223 : // that read then pulls in the data directly.
224 :
225 : nsresult
226 0 : SpdyStream::WriteSegments(nsAHttpSegmentWriter *writer,
227 : PRUint32 count,
228 : PRUint32 *countWritten)
229 : {
230 0 : LOG3(("SpdyStream::WriteSegments %p count=%d state=%x",
231 : this, count, mUpstreamState));
232 :
233 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
234 0 : NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress");
235 :
236 0 : mSegmentWriter = writer;
237 0 : nsresult rv = mTransaction->WriteSegments(writer, count, countWritten);
238 0 : mSegmentWriter = nsnull;
239 0 : return rv;
240 : }
241 :
242 : PLDHashOperator
243 0 : SpdyStream::hdrHashEnumerate(const nsACString &key,
244 : nsAutoPtr<nsCString> &value,
245 : void *closure)
246 : {
247 0 : SpdyStream *self = static_cast<SpdyStream *>(closure);
248 :
249 0 : self->CompressToFrame(key);
250 0 : self->CompressToFrame(value.get());
251 0 : return PL_DHASH_NEXT;
252 : }
253 :
254 : nsresult
255 0 : SpdyStream::ParseHttpRequestHeaders(const char *buf,
256 : PRUint32 avail,
257 : PRUint32 *countUsed)
258 : {
259 : // Returns NS_OK even if the headers are incomplete
260 : // set mSynFrameComplete flag if they are complete
261 :
262 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
263 0 : NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state");
264 :
265 0 : LOG3(("SpdyStream::ParseHttpRequestHeaders %p avail=%d state=%x",
266 : this, avail, mUpstreamState));
267 :
268 0 : mFlatHttpRequestHeaders.Append(buf, avail);
269 :
270 : // We can use the simple double crlf because firefox is the
271 : // only client we are parsing
272 0 : PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
273 :
274 0 : if (endHeader == kNotFound) {
275 : // We don't have all the headers yet
276 0 : LOG3(("SpdyStream::ParseHttpRequestHeaders %p "
277 : "Need more header bytes. Len = %d",
278 : this, mFlatHttpRequestHeaders.Length()));
279 0 : *countUsed = avail;
280 0 : return NS_OK;
281 : }
282 :
283 : // We have recvd all the headers, trim the local
284 : // buffer of the final empty line, and set countUsed to reflect
285 : // the whole header has been consumed.
286 0 : PRUint32 oldLen = mFlatHttpRequestHeaders.Length();
287 0 : mFlatHttpRequestHeaders.SetLength(endHeader + 2);
288 0 : *countUsed = avail - (oldLen - endHeader) + 4;
289 0 : mSynFrameComplete = 1;
290 :
291 : // It is now OK to assign a streamID that we are assured will
292 : // be monotonically increasing amongst syn-streams on this
293 : // session
294 0 : mStreamID = mSession->RegisterStreamID(this);
295 0 : NS_ABORT_IF_FALSE(mStreamID & 1,
296 : "Spdy Stream Channel ID must be odd");
297 :
298 0 : if (mStreamID >= 0x80000000) {
299 : // streamID must fit in 31 bits. This is theoretically possible
300 : // because stream ID assignment is asynchronous to stream creation
301 : // because of the protocol requirement that the ID in syn-stream
302 : // be monotonically increasing. In reality this is really not possible
303 : // because new streams stop being added to a session with 0x10000000 / 2
304 : // IDs still available and no race condition is going to bridge that gap,
305 : // so we can be comfortable on just erroring out for correctness in that
306 : // case.
307 0 : LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
308 0 : return NS_ERROR_UNEXPECTED;
309 : }
310 :
311 : // Now we need to convert the flat http headers into a set
312 : // of SPDY headers.. writing to mTxInlineFrame{sz}
313 :
314 0 : mTxInlineFrame[0] = SpdySession::kFlag_Control;
315 0 : mTxInlineFrame[1] = 2; /* version */
316 0 : mTxInlineFrame[2] = 0;
317 0 : mTxInlineFrame[3] = SpdySession::CONTROL_TYPE_SYN_STREAM;
318 : // 4 to 7 are length and flags, we'll fill that in later
319 :
320 0 : PRUint32 networkOrderID = PR_htonl(mStreamID);
321 0 : memcpy(mTxInlineFrame + 8, &networkOrderID, 4);
322 :
323 : // this is the associated-to field, which is not used sending
324 : // from the client in the http binding
325 0 : memset (mTxInlineFrame + 12, 0, 4);
326 :
327 : // Priority flags are the C0 mask of byte 16.
328 : //
329 : // The other 6 bits of 16 are unused. Spdy/3 will expand
330 : // priority to 4 bits.
331 : //
332 : // When Spdy/3 implements WINDOW_UPDATE the lowest priority
333 : // streams over a threshold (32?) should be given tiny
334 : // receive windows, separate from their spdy priority
335 : //
336 0 : if (mPriority >= nsISupportsPriority::PRIORITY_LOW)
337 0 : mTxInlineFrame[16] = SpdySession::kPri03;
338 0 : else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL)
339 0 : mTxInlineFrame[16] = SpdySession::kPri02;
340 0 : else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH)
341 0 : mTxInlineFrame[16] = SpdySession::kPri01;
342 : else
343 0 : mTxInlineFrame[16] = SpdySession::kPri00;
344 :
345 0 : mTxInlineFrame[17] = 0; /* unused */
346 :
347 0 : const char *methodHeader = mTransaction->RequestHead()->Method().get();
348 :
349 0 : nsCString hostHeader;
350 0 : mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
351 :
352 0 : nsCString versionHeader;
353 0 : if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1)
354 0 : versionHeader = NS_LITERAL_CSTRING("HTTP/1.1");
355 : else
356 0 : versionHeader = NS_LITERAL_CSTRING("HTTP/1.0");
357 :
358 0 : nsClassHashtable<nsCStringHashKey, nsCString> hdrHash;
359 :
360 : // use mRequestHead() to get a sense of how big to make the hash,
361 : // even though we are parsing the actual text stream because
362 : // it is legit to append headers.
363 0 : hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2));
364 :
365 0 : const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
366 :
367 : // need to hash all the headers together to remove duplicates, special
368 : // headers, etc..
369 :
370 0 : PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
371 0 : while (true) {
372 0 : PRInt32 startIndex = crlfIndex + 2;
373 :
374 0 : crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
375 0 : if (crlfIndex == -1)
376 0 : break;
377 :
378 : PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
379 0 : crlfIndex - startIndex);
380 0 : if (colonIndex == -1)
381 0 : break;
382 :
383 : nsDependentCSubstring name = Substring(beginBuffer + startIndex,
384 0 : beginBuffer + colonIndex);
385 : // all header names are lower case in spdy
386 0 : ToLowerCase(name);
387 :
388 0 : if (name.Equals("method") ||
389 0 : name.Equals("version") ||
390 0 : name.Equals("scheme") ||
391 0 : name.Equals("keep-alive") ||
392 0 : name.Equals("accept-encoding") ||
393 0 : name.Equals("te") ||
394 0 : name.Equals("connection") ||
395 0 : name.Equals("proxy-connection") ||
396 0 : name.Equals("url"))
397 0 : continue;
398 :
399 0 : nsCString *val = hdrHash.Get(name);
400 0 : if (!val) {
401 0 : val = new nsCString();
402 0 : hdrHash.Put(name, val);
403 : }
404 :
405 0 : PRInt32 valueIndex = colonIndex + 1;
406 0 : while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
407 0 : ++valueIndex;
408 :
409 : nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
410 0 : beginBuffer + crlfIndex);
411 0 : if (!val->IsEmpty())
412 0 : val->Append(static_cast<char>(0));
413 0 : val->Append(v);
414 :
415 0 : if (name.Equals("content-length")) {
416 : PRInt64 len;
417 0 : if (nsHttp::ParseInt64(val->get(), nsnull, &len))
418 0 : mRequestBodyLenRemaining = len;
419 : }
420 : }
421 :
422 0 : mTxInlineFrameUsed = 18;
423 :
424 : // Do not naively log the request headers here beacuse they might
425 : // contain auth. The http transaction already logs the sanitized request
426 : // headers at this same level so it is not necessary to do so here.
427 :
428 : // The header block length
429 0 : PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */
430 0 : CompressToFrame(count);
431 :
432 : // method, scheme, url, and version headers for request line
433 :
434 0 : CompressToFrame(NS_LITERAL_CSTRING("method"));
435 0 : CompressToFrame(methodHeader, strlen(methodHeader));
436 0 : CompressToFrame(NS_LITERAL_CSTRING("scheme"));
437 0 : CompressToFrame(NS_LITERAL_CSTRING("https"));
438 0 : CompressToFrame(NS_LITERAL_CSTRING("url"));
439 0 : CompressToFrame(mTransaction->RequestHead()->RequestURI());
440 0 : CompressToFrame(NS_LITERAL_CSTRING("version"));
441 0 : CompressToFrame(versionHeader);
442 :
443 0 : hdrHash.Enumerate(hdrHashEnumerate, this);
444 0 : CompressFlushFrame();
445 :
446 : // 4 to 7 are length and flags, which we can now fill in
447 0 : (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] =
448 0 : PR_htonl(mTxInlineFrameUsed - 8);
449 :
450 0 : NS_ABORT_IF_FALSE(!mTxInlineFrame[4],
451 : "Size greater than 24 bits");
452 :
453 : // Determine whether to put the fin bit on the syn stream frame or whether
454 : // to wait for a data packet to put it on.
455 :
456 0 : if (mTransaction->RequestHead()->Method() == nsHttp::Get ||
457 0 : mTransaction->RequestHead()->Method() == nsHttp::Connect ||
458 0 : mTransaction->RequestHead()->Method() == nsHttp::Head) {
459 : // for GET, CONNECT, and HEAD place the fin bit right on the
460 : // syn stream packet
461 :
462 0 : mSentFinOnData = 1;
463 0 : mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN;
464 : }
465 0 : else if (mTransaction->RequestHead()->Method() == nsHttp::Post ||
466 0 : mTransaction->RequestHead()->Method() == nsHttp::Put ||
467 0 : mTransaction->RequestHead()->Method() == nsHttp::Options) {
468 : // place fin in a data frame even for 0 length messages, I've seen
469 : // the google gateway be unhappy with fin-on-syn for 0 length POST
470 : }
471 0 : else if (!mRequestBodyLenRemaining) {
472 : // for other HTTP extension methods, rely on the content-length
473 : // to determine whether or not to put fin on syn
474 0 : mSentFinOnData = 1;
475 0 : mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN;
476 : }
477 :
478 0 : Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameUsed - 18);
479 :
480 : // The size of the input headers is approximate
481 : PRUint32 ratio =
482 : (mTxInlineFrameUsed - 18) * 100 /
483 0 : (11 + mTransaction->RequestHead()->RequestURI().Length() +
484 0 : mFlatHttpRequestHeaders.Length());
485 :
486 0 : Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
487 0 : return NS_OK;
488 : }
489 :
490 : void
491 0 : SpdyStream::UpdateTransportReadEvents(PRUint32 count)
492 : {
493 0 : mTotalRead += count;
494 :
495 0 : mTransaction->OnTransportStatus(mSocketTransport,
496 : NS_NET_STATUS_RECEIVING_FROM,
497 0 : mTotalRead);
498 0 : }
499 :
500 : void
501 0 : SpdyStream::UpdateTransportSendEvents(PRUint32 count)
502 : {
503 0 : mTotalSent += count;
504 :
505 0 : if (mUpstreamState != SENDING_FIN_STREAM)
506 0 : mTransaction->OnTransportStatus(mSocketTransport,
507 : NS_NET_STATUS_SENDING_TO,
508 0 : mTotalSent);
509 :
510 0 : if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
511 0 : mSentWaitingFor = 1;
512 0 : mTransaction->OnTransportStatus(mSocketTransport,
513 : NS_NET_STATUS_WAITING_FOR,
514 0 : LL_ZERO);
515 : }
516 0 : }
517 :
518 : nsresult
519 0 : SpdyStream::TransmitFrame(const char *buf,
520 : PRUint32 *countUsed)
521 : {
522 : // If TransmitFrame returns SUCCESS than all the data is sent (or at least
523 : // buffered at the session level), if it returns WOULD_BLOCK then none of
524 : // the data is sent.
525 :
526 : // You can call this function with no data and no out parameter in order to
527 : // flush internal buffers that were previously blocked on writing. You can
528 : // of course feed new data to it as well.
529 :
530 0 : NS_ABORT_IF_FALSE(mTxInlineFrameUsed, "empty stream frame in transmit");
531 0 : NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader");
532 0 : NS_ABORT_IF_FALSE((buf && countUsed) || (!buf && !countUsed),
533 : "TransmitFrame arguments inconsistent");
534 :
535 : PRUint32 transmittedCount;
536 : nsresult rv;
537 :
538 0 : LOG3(("SpdyStream::TransmitFrame %p inline=%d stream=%d",
539 : this, mTxInlineFrameUsed, mTxStreamFrameSize));
540 0 : if (countUsed)
541 0 : *countUsed = 0;
542 :
543 : // In the (relatively common) event that we have a small amount of data
544 : // split between the inlineframe and the streamframe, then move the stream
545 : // data into the inlineframe via copy in order to coalesce into one write.
546 : // Given the interaction with ssl this is worth the small copy cost.
547 0 : if (mTxStreamFrameSize && mTxInlineFrameUsed &&
548 : mTxStreamFrameSize < SpdySession::kDefaultBufferSize &&
549 : mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
550 0 : LOG3(("Coalesce Transmit"));
551 0 : memcpy (mTxInlineFrame + mTxInlineFrameUsed,
552 0 : buf, mTxStreamFrameSize);
553 0 : if (countUsed)
554 0 : *countUsed += mTxStreamFrameSize;
555 0 : mTxInlineFrameUsed += mTxStreamFrameSize;
556 0 : mTxStreamFrameSize = 0;
557 : }
558 :
559 : rv = mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize +
560 0 : mTxInlineFrameUsed);
561 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
562 0 : mSession->TransactionHasDataToWrite(this);
563 0 : if (NS_FAILED(rv)) // this will include WOULD_BLOCK
564 0 : return rv;
565 :
566 : // This function calls mSegmentReader->OnReadSegment to report the actual SPDY
567 : // bytes through to the SpdySession and then the HttpConnection which calls
568 : // the socket write function. It will accept all of the inline and stream
569 : // data because of the above 'commitment' even if it has to buffer
570 :
571 : rv = mSegmentReader->OnReadSegment(mTxInlineFrame, mTxInlineFrameUsed,
572 0 : &transmittedCount);
573 0 : LOG3(("SpdyStream::TransmitFrame for inline session=%p "
574 : "stream=%p result %x len=%d",
575 : mSession, this, rv, transmittedCount));
576 :
577 0 : NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK,
578 : "inconsistent inline commitment result");
579 :
580 0 : if (NS_FAILED(rv))
581 0 : return rv;
582 :
583 0 : NS_ABORT_IF_FALSE(transmittedCount == mTxInlineFrameUsed,
584 : "inconsistent inline commitment count");
585 :
586 : SpdySession::LogIO(mSession, this, "Writing from Inline Buffer",
587 0 : mTxInlineFrame, transmittedCount);
588 :
589 0 : if (mTxStreamFrameSize) {
590 0 : if (!buf) {
591 : // this cannot happen
592 0 : NS_ABORT_IF_FALSE(false, "Stream transmit with null buf argument to "
593 : "TransmitFrame()");
594 0 : LOG(("Stream transmit with null buf argument to TransmitFrame()\n"));
595 0 : return NS_ERROR_UNEXPECTED;
596 : }
597 :
598 : rv = mSegmentReader->OnReadSegment(buf, mTxStreamFrameSize,
599 0 : &transmittedCount);
600 :
601 0 : LOG3(("SpdyStream::TransmitFrame for regular session=%p "
602 : "stream=%p result %x len=%d",
603 : mSession, this, rv, transmittedCount));
604 :
605 0 : NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK,
606 : "inconsistent stream commitment result");
607 :
608 0 : if (NS_FAILED(rv))
609 0 : return rv;
610 :
611 0 : NS_ABORT_IF_FALSE(transmittedCount == mTxStreamFrameSize,
612 : "inconsistent stream commitment count");
613 :
614 : SpdySession::LogIO(mSession, this, "Writing from Transaction Buffer",
615 0 : buf, transmittedCount);
616 :
617 0 : *countUsed += mTxStreamFrameSize;
618 : }
619 :
620 : // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
621 0 : UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
622 :
623 0 : mTxInlineFrameUsed = 0;
624 0 : mTxStreamFrameSize = 0;
625 :
626 0 : return NS_OK;
627 : }
628 :
629 : void
630 0 : SpdyStream::ChangeState(enum stateType newState)
631 : {
632 0 : LOG3(("SpdyStream::ChangeState() %p from %X to %X",
633 : this, mUpstreamState, newState));
634 0 : mUpstreamState = newState;
635 : return;
636 : }
637 :
638 : void
639 0 : SpdyStream::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame)
640 : {
641 0 : LOG3(("SpdyStream::GenerateDataFrameHeader %p len=%d last=%d",
642 : this, dataLength, lastFrame));
643 :
644 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
645 0 : NS_ABORT_IF_FALSE(!mTxInlineFrameUsed, "inline frame not empty");
646 0 : NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty");
647 0 : NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits");
648 :
649 0 : (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID);
650 0 : (reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] =
651 0 : PR_htonl(dataLength);
652 :
653 0 : NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80),
654 : "control bit set unexpectedly");
655 0 : NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly");
656 :
657 0 : mTxInlineFrameUsed = 8;
658 0 : mTxStreamFrameSize = dataLength;
659 :
660 0 : if (lastFrame) {
661 0 : mTxInlineFrame[4] |= SpdySession::kFlag_Data_FIN;
662 0 : if (dataLength)
663 0 : mSentFinOnData = 1;
664 : }
665 0 : }
666 :
667 : void
668 0 : SpdyStream::CompressToFrame(const nsACString &str)
669 : {
670 0 : CompressToFrame(str.BeginReading(), str.Length());
671 0 : }
672 :
673 : void
674 0 : SpdyStream::CompressToFrame(const nsACString *str)
675 : {
676 0 : CompressToFrame(str->BeginReading(), str->Length());
677 0 : }
678 :
679 : // Dictionary taken from
680 : // http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
681 : // Name/Value Header Block Format
682 : // spec indicates that the compression dictionary is not null terminated
683 : // but in reality it is. see:
684 : // https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs
685 :
686 : const char *SpdyStream::kDictionary =
687 : "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
688 : "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
689 : "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
690 : "-agent10010120020120220320420520630030130230330430530630740040140240340440"
691 : "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
692 : "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
693 : "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
694 : "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
695 : "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
696 : "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
697 : "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
698 : "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
699 : ".1statusversionurl";
700 :
701 : // use for zlib data types
702 : void *
703 0 : SpdyStream::zlib_allocator(void *opaque, uInt items, uInt size)
704 : {
705 0 : return moz_xmalloc(items * size);
706 : }
707 :
708 : // use for zlib data types
709 : void
710 0 : SpdyStream::zlib_destructor(void *opaque, void *addr)
711 : {
712 0 : moz_free(addr);
713 0 : }
714 :
715 : void
716 0 : SpdyStream::ExecuteCompress(PRUint32 flushMode)
717 : {
718 : // Expect mZlib->avail_in and mZlib->next_in to be set.
719 : // Append the compressed version of next_in to mTxInlineFrame
720 :
721 0 : do
722 : {
723 0 : PRUint32 avail = mTxInlineFrameSize - mTxInlineFrameUsed;
724 0 : if (avail < 1) {
725 : SpdySession::EnsureBuffer(mTxInlineFrame,
726 : mTxInlineFrameSize + 2000,
727 : mTxInlineFrameUsed,
728 0 : mTxInlineFrameSize);
729 0 : avail = mTxInlineFrameSize - mTxInlineFrameUsed;
730 : }
731 :
732 0 : mZlib->next_out = reinterpret_cast<unsigned char *> (mTxInlineFrame.get()) +
733 0 : mTxInlineFrameUsed;
734 0 : mZlib->avail_out = avail;
735 0 : deflate(mZlib, flushMode);
736 0 : mTxInlineFrameUsed += avail - mZlib->avail_out;
737 0 : } while (mZlib->avail_in > 0 || !mZlib->avail_out);
738 0 : }
739 :
740 : void
741 0 : SpdyStream::CompressToFrame(PRUint16 data)
742 : {
743 : // convert the data to network byte order and write that
744 : // to the compressed stream
745 :
746 0 : data = PR_htons(data);
747 :
748 0 : mZlib->next_in = reinterpret_cast<unsigned char *> (&data);
749 0 : mZlib->avail_in = 2;
750 0 : ExecuteCompress(Z_NO_FLUSH);
751 0 : }
752 :
753 :
754 : void
755 0 : SpdyStream::CompressToFrame(const char *data, PRUint32 len)
756 : {
757 : // Format calls for a network ordered 16 bit length
758 : // followed by the utf8 string
759 :
760 : // for now, silently truncate headers greater than 64KB. Spdy/3 will
761 : // fix this by making the len a 32 bit quantity
762 0 : if (len > 0xffff)
763 0 : len = 0xffff;
764 :
765 0 : PRUint16 networkLen = PR_htons(len);
766 :
767 : // write out the length
768 0 : mZlib->next_in = reinterpret_cast<unsigned char *> (&networkLen);
769 0 : mZlib->avail_in = 2;
770 0 : ExecuteCompress(Z_NO_FLUSH);
771 :
772 : // write out the data
773 0 : mZlib->next_in = (unsigned char *)data;
774 0 : mZlib->avail_in = len;
775 0 : ExecuteCompress(Z_NO_FLUSH);
776 0 : }
777 :
778 : void
779 0 : SpdyStream::CompressFlushFrame()
780 : {
781 0 : mZlib->next_in = (unsigned char *) "";
782 0 : mZlib->avail_in = 0;
783 0 : ExecuteCompress(Z_SYNC_FLUSH);
784 0 : }
785 :
786 : void
787 0 : SpdyStream::Close(nsresult reason)
788 : {
789 0 : mTransaction->Close(reason);
790 0 : }
791 :
792 : //-----------------------------------------------------------------------------
793 : // nsAHttpSegmentReader
794 : //-----------------------------------------------------------------------------
795 :
796 : nsresult
797 0 : SpdyStream::OnReadSegment(const char *buf,
798 : PRUint32 count,
799 : PRUint32 *countRead)
800 : {
801 0 : LOG3(("SpdyStream::OnReadSegment %p count=%d state=%x",
802 : this, count, mUpstreamState));
803 :
804 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
805 0 : NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader");
806 :
807 0 : nsresult rv = NS_ERROR_UNEXPECTED;
808 : PRUint32 dataLength;
809 :
810 0 : switch (mUpstreamState) {
811 : case GENERATING_SYN_STREAM:
812 : // The buffer is the HTTP request stream, including at least part of the
813 : // HTTP request header. This state's job is to build a SYN_STREAM frame
814 : // from the header information. count is the number of http bytes available
815 : // (which may include more than the header), and in countRead we return
816 : // the number of those bytes that we consume (i.e. the portion that are
817 : // header bytes)
818 :
819 0 : rv = ParseHttpRequestHeaders(buf, count, countRead);
820 0 : if (NS_FAILED(rv))
821 0 : return rv;
822 0 : LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
823 : this, *countRead, count, mSynFrameComplete));
824 0 : if (mSynFrameComplete) {
825 0 : NS_ABORT_IF_FALSE(mTxInlineFrameUsed,
826 : "OnReadSegment SynFrameComplete 0b");
827 0 : rv = TransmitFrame(nsnull, nsnull);
828 0 : NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
829 : "Transmit Frame should be all or nothing");
830 :
831 : // normalize a blocked write into an ok one if we have consumed the data
832 : // while parsing headers as some code will take WOULD_BLOCK to mean an
833 : // error with nothing processed.
834 : // (e.g. nsHttpTransaction::ReadRequestSegment())
835 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
836 0 : rv = NS_OK;
837 :
838 : // mTxInlineFrameUsed > 0 means the current frame is in progress
839 : // of sending. mTxInlineFrameUsed is dropped to 0 after both the frame
840 : // and its payload (if any) are completely sent out. Here during
841 : // GENERATING_SYN_STREAM state we are sending just the http headers.
842 : // Only when the frame is completely sent out do we proceed to
843 : // GENERATING_REQUEST_BODY state.
844 :
845 0 : if (mTxInlineFrameUsed)
846 0 : ChangeState(SENDING_SYN_STREAM);
847 : else
848 0 : ChangeState(GENERATING_REQUEST_BODY);
849 0 : break;
850 : }
851 0 : NS_ABORT_IF_FALSE(*countRead == count,
852 : "Header parsing not complete but unused data");
853 0 : break;
854 :
855 : case GENERATING_REQUEST_BODY:
856 0 : dataLength = NS_MIN(count, mChunkSize);
857 0 : LOG3(("SpdyStream %p id %x request len remaining %d, "
858 : "count avail %d, chunk used %d",
859 : this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
860 0 : if (dataLength > mRequestBodyLenRemaining)
861 0 : return NS_ERROR_UNEXPECTED;
862 0 : mRequestBodyLenRemaining -= dataLength;
863 0 : GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
864 0 : ChangeState(SENDING_REQUEST_BODY);
865 : // NO BREAK
866 :
867 : case SENDING_REQUEST_BODY:
868 0 : NS_ABORT_IF_FALSE(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
869 0 : rv = TransmitFrame(buf, countRead);
870 0 : NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
871 : "Transmit Frame should be all or nothing");
872 :
873 0 : LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
874 : "Header is %d Body is %d.",
875 : rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
876 :
877 : // normalize a partial write with a WOULD_BLOCK into just a partial write
878 : // as some code will take WOULD_BLOCK to mean an error with nothing
879 : // written (e.g. nsHttpTransaction::ReadRequestSegment()
880 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
881 0 : rv = NS_OK;
882 :
883 : // If that frame was all sent, look for another one
884 0 : if (!mTxInlineFrameUsed)
885 0 : ChangeState(GENERATING_REQUEST_BODY);
886 0 : break;
887 :
888 : case SENDING_SYN_STREAM:
889 0 : rv = NS_BASE_STREAM_WOULD_BLOCK;
890 0 : break;
891 :
892 : case SENDING_FIN_STREAM:
893 0 : NS_ABORT_IF_FALSE(false,
894 : "resuming partial fin stream out of OnReadSegment");
895 0 : break;
896 :
897 : default:
898 0 : NS_ABORT_IF_FALSE(false, "SpdyStream::OnReadSegment non-write state");
899 0 : break;
900 : }
901 :
902 0 : return rv;
903 : }
904 :
905 : //-----------------------------------------------------------------------------
906 : // nsAHttpSegmentWriter
907 : //-----------------------------------------------------------------------------
908 :
909 : nsresult
910 0 : SpdyStream::OnWriteSegment(char *buf,
911 : PRUint32 count,
912 : PRUint32 *countWritten)
913 : {
914 0 : LOG3(("SpdyStream::OnWriteSegment %p count=%d state=%x",
915 : this, count, mUpstreamState));
916 :
917 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
918 0 : NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter");
919 :
920 0 : return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
921 : }
922 :
923 : } // namespace mozilla::net
924 : } // namespace mozilla
925 :
|