1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.squareup.okhttp.internal.spdy;
17
18import com.squareup.okhttp.Protocol;
19import com.squareup.okhttp.internal.Util;
20import java.io.IOException;
21import java.io.UnsupportedEncodingException;
22import java.net.ProtocolException;
23import java.util.List;
24import java.util.zip.Deflater;
25import okio.Buffer;
26import okio.BufferedSink;
27import okio.BufferedSource;
28import okio.ByteString;
29import okio.DeflaterSink;
30import okio.Okio;
31
32/**
33 * Read and write spdy/3.1 frames.
34 * http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1
35 */
36public final class Spdy3 implements Variant {
37
38  @Override public Protocol getProtocol() {
39    return Protocol.SPDY_3;
40  }
41
42  static final int TYPE_DATA = 0x0;
43  static final int TYPE_SYN_STREAM = 0x1;
44  static final int TYPE_SYN_REPLY = 0x2;
45  static final int TYPE_RST_STREAM = 0x3;
46  static final int TYPE_SETTINGS = 0x4;
47  static final int TYPE_PING = 0x6;
48  static final int TYPE_GOAWAY = 0x7;
49  static final int TYPE_HEADERS = 0x8;
50  static final int TYPE_WINDOW_UPDATE = 0x9;
51
52  static final int FLAG_FIN = 0x1;
53  static final int FLAG_UNIDIRECTIONAL = 0x2;
54
55  static final int VERSION = 3;
56
57  static final byte[] DICTIONARY;
58  static {
59    try {
60      DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
61          + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
62          + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
63          + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
64          + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
65          + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
66          + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
67          + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
68          + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
69          + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
70          + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
71          + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
72          + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
73          + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
74          + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
75          + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
76          + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
77          + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
78          + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
79          + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
80          + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
81          + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
82          + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
83          + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
84          + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
85          + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
86          + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
87          + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
88          + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
89          + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
90          + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
91          + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
92          + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name());
93    } catch (UnsupportedEncodingException e) {
94      throw new AssertionError();
95    }
96  }
97
98  @Override public FrameReader newReader(BufferedSource source, boolean client) {
99    return new Reader(source, client);
100  }
101
102  @Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
103    return new Writer(sink, client);
104  }
105
106  /** Read spdy/3 frames. */
107  static final class Reader implements FrameReader {
108    private final BufferedSource source;
109    private final boolean client;
110    private final NameValueBlockReader headerBlockReader;
111
112    Reader(BufferedSource source, boolean client) {
113      this.source = source;
114      this.headerBlockReader = new NameValueBlockReader(this.source);
115      this.client = client;
116    }
117
118    @Override public void readConnectionPreface() {
119    }
120
121    /**
122     * Send the next frame to {@code handler}. Returns true unless there are no
123     * more frames on the stream.
124     */
125    @Override public boolean nextFrame(Handler handler) throws IOException {
126      int w1;
127      int w2;
128      try {
129        w1 = source.readInt();
130        w2 = source.readInt();
131      } catch (IOException e) {
132        return false; // This might be a normal socket close.
133      }
134
135      boolean control = (w1 & 0x80000000) != 0;
136      int flags = (w2 & 0xff000000) >>> 24;
137      int length = (w2 & 0xffffff);
138
139      if (control) {
140        int version = (w1 & 0x7fff0000) >>> 16;
141        int type = (w1 & 0xffff);
142
143        if (version != 3) {
144          throw new ProtocolException("version != 3: " + version);
145        }
146
147        switch (type) {
148          case TYPE_SYN_STREAM:
149            readSynStream(handler, flags, length);
150            return true;
151
152          case TYPE_SYN_REPLY:
153            readSynReply(handler, flags, length);
154            return true;
155
156          case TYPE_RST_STREAM:
157            readRstStream(handler, flags, length);
158            return true;
159
160          case TYPE_SETTINGS:
161            readSettings(handler, flags, length);
162            return true;
163
164          case TYPE_PING:
165            readPing(handler, flags, length);
166            return true;
167
168          case TYPE_GOAWAY:
169            readGoAway(handler, flags, length);
170            return true;
171
172          case TYPE_HEADERS:
173            readHeaders(handler, flags, length);
174            return true;
175
176          case TYPE_WINDOW_UPDATE:
177            readWindowUpdate(handler, flags, length);
178            return true;
179
180          default:
181            source.skip(length);
182            return true;
183        }
184      } else {
185        int streamId = w1 & 0x7fffffff;
186        boolean inFinished = (flags & FLAG_FIN) != 0;
187        handler.data(inFinished, streamId, source, length);
188        return true;
189      }
190    }
191
192    private void readSynStream(Handler handler, int flags, int length) throws IOException {
193      int w1 = source.readInt();
194      int w2 = source.readInt();
195      int streamId = w1 & 0x7fffffff;
196      int associatedStreamId = w2 & 0x7fffffff;
197      source.readShort(); // int priority = (s3 & 0xe000) >>> 13; int slot = s3 & 0xff;
198      List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 10);
199
200      boolean inFinished = (flags & FLAG_FIN) != 0;
201      boolean outFinished = (flags & FLAG_UNIDIRECTIONAL) != 0;
202      handler.headers(outFinished, inFinished, streamId, associatedStreamId, headerBlock,
203          HeadersMode.SPDY_SYN_STREAM);
204    }
205
206    private void readSynReply(Handler handler, int flags, int length) throws IOException {
207      int w1 = source.readInt();
208      int streamId = w1 & 0x7fffffff;
209      List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 4);
210      boolean inFinished = (flags & FLAG_FIN) != 0;
211      handler.headers(false, inFinished, streamId, -1, headerBlock, HeadersMode.SPDY_REPLY);
212    }
213
214    private void readRstStream(Handler handler, int flags, int length) throws IOException {
215      if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length);
216      int streamId = source.readInt() & 0x7fffffff;
217      int errorCodeInt = source.readInt();
218      ErrorCode errorCode = ErrorCode.fromSpdy3Rst(errorCodeInt);
219      if (errorCode == null) {
220        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
221      }
222      handler.rstStream(streamId, errorCode);
223    }
224
225    private void readHeaders(Handler handler, int flags, int length) throws IOException {
226      int w1 = source.readInt();
227      int streamId = w1 & 0x7fffffff;
228      List<Header> headerBlock = headerBlockReader.readNameValueBlock(length - 4);
229      handler.headers(false, false, streamId, -1, headerBlock, HeadersMode.SPDY_HEADERS);
230    }
231
232    private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
233      if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length);
234      int w1 = source.readInt();
235      int w2 = source.readInt();
236      int streamId = w1 & 0x7fffffff;
237      long increment = w2 & 0x7fffffff;
238      if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
239      handler.windowUpdate(streamId, increment);
240    }
241
242    private void readPing(Handler handler, int flags, int length) throws IOException {
243      if (length != 4) throw ioException("TYPE_PING length: %d != 4", length);
244      int id = source.readInt();
245      boolean ack = client == ((id & 1) == 1);
246      handler.ping(ack, id, 0);
247    }
248
249    private void readGoAway(Handler handler, int flags, int length) throws IOException {
250      if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
251      int lastGoodStreamId = source.readInt() & 0x7fffffff;
252      int errorCodeInt = source.readInt();
253      ErrorCode errorCode = ErrorCode.fromSpdyGoAway(errorCodeInt);
254      if (errorCode == null) {
255        throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
256      }
257      handler.goAway(lastGoodStreamId, errorCode, ByteString.EMPTY);
258    }
259
260    private void readSettings(Handler handler, int flags, int length) throws IOException {
261      int numberOfEntries = source.readInt();
262      if (length != 4 + 8 * numberOfEntries) {
263        throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries);
264      }
265      Settings settings = new Settings();
266      for (int i = 0; i < numberOfEntries; i++) {
267        int w1 = source.readInt();
268        int value = source.readInt();
269        int idFlags = (w1 & 0xff000000) >>> 24;
270        int id = w1 & 0xffffff;
271        settings.set(id, idFlags, value);
272      }
273      boolean clearPrevious = (flags & Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0;
274      handler.settings(clearPrevious, settings);
275    }
276
277    private static IOException ioException(String message, Object... args) throws IOException {
278      throw new IOException(String.format(message, args));
279    }
280
281    @Override public void close() throws IOException {
282      headerBlockReader.close();
283    }
284  }
285
286  /** Write spdy/3 frames. */
287  static final class Writer implements FrameWriter {
288    private final BufferedSink sink;
289    private final Buffer headerBlockBuffer;
290    private final BufferedSink headerBlockOut;
291    private final boolean client;
292    private boolean closed;
293
294    Writer(BufferedSink sink, boolean client) {
295      this.sink = sink;
296      this.client = client;
297
298      Deflater deflater = new Deflater();
299      deflater.setDictionary(DICTIONARY);
300      headerBlockBuffer = new Buffer();
301      headerBlockOut = Okio.buffer(new DeflaterSink(headerBlockBuffer, deflater));
302    }
303
304    @Override public void ackSettings(Settings peerSettings) {
305      // Do nothing: no ACK for SPDY/3 settings.
306    }
307
308    @Override
309    public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
310        throws IOException {
311      // Do nothing: no push promise for SPDY/3.
312    }
313
314    @Override public synchronized void connectionPreface() {
315      // Do nothing: no connection preface for SPDY/3.
316    }
317
318    @Override public synchronized void flush() throws IOException {
319      if (closed) throw new IOException("closed");
320      sink.flush();
321    }
322
323    @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
324        int streamId, int associatedStreamId, List<Header> headerBlock)
325        throws IOException {
326      if (closed) throw new IOException("closed");
327      writeNameValueBlockToBuffer(headerBlock);
328      int length = (int) (10 + headerBlockBuffer.size());
329      int type = TYPE_SYN_STREAM;
330      int flags = (outFinished ? FLAG_FIN : 0) | (inFinished ? FLAG_UNIDIRECTIONAL : 0);
331
332      int unused = 0;
333      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
334      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
335      sink.writeInt(streamId & 0x7fffffff);
336      sink.writeInt(associatedStreamId & 0x7fffffff);
337      sink.writeShort((unused & 0x7) << 13 | (unused & 0x1f) << 8 | (unused & 0xff));
338      sink.writeAll(headerBlockBuffer);
339      sink.flush();
340    }
341
342    @Override public synchronized void synReply(boolean outFinished, int streamId,
343        List<Header> headerBlock) throws IOException {
344      if (closed) throw new IOException("closed");
345      writeNameValueBlockToBuffer(headerBlock);
346      int type = TYPE_SYN_REPLY;
347      int flags = (outFinished ? FLAG_FIN : 0);
348      int length = (int) (headerBlockBuffer.size() + 4);
349
350      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
351      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
352      sink.writeInt(streamId & 0x7fffffff);
353      sink.writeAll(headerBlockBuffer);
354      sink.flush();
355    }
356
357    @Override public synchronized void headers(int streamId, List<Header> headerBlock)
358        throws IOException {
359      if (closed) throw new IOException("closed");
360      writeNameValueBlockToBuffer(headerBlock);
361      int flags = 0;
362      int type = TYPE_HEADERS;
363      int length = (int) (headerBlockBuffer.size() + 4);
364
365      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
366      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
367      sink.writeInt(streamId & 0x7fffffff);
368      sink.writeAll(headerBlockBuffer);
369    }
370
371    @Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
372        throws IOException {
373      if (closed) throw new IOException("closed");
374      if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException();
375      int flags = 0;
376      int type = TYPE_RST_STREAM;
377      int length = 8;
378      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
379      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
380      sink.writeInt(streamId & 0x7fffffff);
381      sink.writeInt(errorCode.spdyRstCode);
382      sink.flush();
383    }
384
385    @Override public int maxDataLength() {
386      return 16383;
387    }
388
389    @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
390        int byteCount) throws IOException {
391      int flags = (outFinished ? FLAG_FIN : 0);
392      sendDataFrame(streamId, flags, source, byteCount);
393    }
394
395    void sendDataFrame(int streamId, int flags, Buffer buffer, int byteCount)
396        throws IOException {
397      if (closed) throw new IOException("closed");
398      if (byteCount > 0xffffffL) {
399        throw new IllegalArgumentException("FRAME_TOO_LARGE max size is 16Mib: " + byteCount);
400      }
401      sink.writeInt(streamId & 0x7fffffff);
402      sink.writeInt((flags & 0xff) << 24 | byteCount & 0xffffff);
403      if (byteCount > 0) {
404        sink.write(buffer, byteCount);
405      }
406    }
407
408    private void writeNameValueBlockToBuffer(List<Header> headerBlock) throws IOException {
409      if (headerBlockBuffer.size() != 0) throw new IllegalStateException();
410      headerBlockOut.writeInt(headerBlock.size());
411      for (int i = 0, size = headerBlock.size(); i < size; i++) {
412        ByteString name = headerBlock.get(i).name;
413        headerBlockOut.writeInt(name.size());
414        headerBlockOut.write(name);
415        ByteString value = headerBlock.get(i).value;
416        headerBlockOut.writeInt(value.size());
417        headerBlockOut.write(value);
418      }
419      headerBlockOut.flush();
420    }
421
422    @Override public synchronized void settings(Settings settings) throws IOException {
423      if (closed) throw new IOException("closed");
424      int type = TYPE_SETTINGS;
425      int flags = 0;
426      int size = settings.size();
427      int length = 4 + size * 8;
428      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
429      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
430      sink.writeInt(size);
431      for (int i = 0; i <= Settings.COUNT; i++) {
432        if (!settings.isSet(i)) continue;
433        int settingsFlags = settings.flags(i);
434        sink.writeInt((settingsFlags & 0xff) << 24 | (i & 0xffffff));
435        sink.writeInt(settings.get(i));
436      }
437      sink.flush();
438    }
439
440    @Override public synchronized void ping(boolean reply, int payload1, int payload2)
441        throws IOException {
442      if (closed) throw new IOException("closed");
443      boolean payloadIsReply = client != ((payload1 & 1) == 1);
444      if (reply != payloadIsReply) throw new IllegalArgumentException("payload != reply");
445      int type = TYPE_PING;
446      int flags = 0;
447      int length = 4;
448      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
449      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
450      sink.writeInt(payload1);
451      sink.flush();
452    }
453
454    @Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
455        byte[] ignored) throws IOException {
456      if (closed) throw new IOException("closed");
457      if (errorCode.spdyGoAwayCode == -1) {
458        throw new IllegalArgumentException("errorCode.spdyGoAwayCode == -1");
459      }
460      int type = TYPE_GOAWAY;
461      int flags = 0;
462      int length = 8;
463      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
464      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
465      sink.writeInt(lastGoodStreamId);
466      sink.writeInt(errorCode.spdyGoAwayCode);
467      sink.flush();
468    }
469
470    @Override public synchronized void windowUpdate(int streamId, long increment)
471        throws IOException {
472      if (closed) throw new IOException("closed");
473      if (increment == 0 || increment > 0x7fffffffL) {
474        throw new IllegalArgumentException(
475            "windowSizeIncrement must be between 1 and 0x7fffffff: " + increment);
476      }
477      int type = TYPE_WINDOW_UPDATE;
478      int flags = 0;
479      int length = 8;
480      sink.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
481      sink.writeInt((flags & 0xff) << 24 | length & 0xffffff);
482      sink.writeInt(streamId);
483      sink.writeInt((int) increment);
484      sink.flush();
485    }
486
487    @Override public synchronized void close() throws IOException {
488      closed = true;
489      Util.closeAll(sink, headerBlockOut);
490    }
491  }
492}
493