Home | History | Annotate | Download | only in spdy
      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  */
     16 
     17 package com.squareup.okhttp.internal.spdy;
     18 
     19 import com.squareup.okhttp.internal.Util;
     20 import java.io.Closeable;
     21 import java.io.DataInputStream;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.io.UnsupportedEncodingException;
     25 import java.net.ProtocolException;
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 import java.util.logging.Logger;
     29 import java.util.zip.DataFormatException;
     30 import java.util.zip.Inflater;
     31 import java.util.zip.InflaterInputStream;
     32 
     33 /** Read spdy/3 frames. */
     34 final class SpdyReader implements Closeable {
     35   static final byte[] DICTIONARY;
     36   static {
     37     try {
     38       DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
     39           + "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
     40           + "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
     41           + "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
     42           + "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
     43           + "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
     44           + "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
     45           + "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
     46           + "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
     47           + "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
     48           + "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
     49           + "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
     50           + "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
     51           + "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
     52           + "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
     53           + "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
     54           + "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
     55           + "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
     56           + "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
     57           + "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
     58           + "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
     59           + "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
     60           + "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
     61           + "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
     62           + "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
     63           + "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
     64           + "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
     65           + "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
     66           + "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
     67           + "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
     68           + "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
     69           + ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
     70           + "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8.name());
     71     } catch (UnsupportedEncodingException e) {
     72       throw new AssertionError();
     73     }
     74   }
     75 
     76   private final DataInputStream in;
     77   private final DataInputStream nameValueBlockIn;
     78   private int compressedLimit;
     79 
     80   SpdyReader(InputStream in) {
     81     this.in = new DataInputStream(in);
     82     this.nameValueBlockIn = newNameValueBlockStream();
     83   }
     84 
     85   /**
     86    * Send the next frame to {@code handler}. Returns true unless there are no
     87    * more frames on the stream.
     88    */
     89   public boolean nextFrame(Handler handler) throws IOException {
     90     int w1;
     91     try {
     92       w1 = in.readInt();
     93     } catch (IOException e) {
     94       return false; // This might be a normal socket close.
     95     }
     96     int w2 = in.readInt();
     97 
     98     boolean control = (w1 & 0x80000000) != 0;
     99     int flags = (w2 & 0xff000000) >>> 24;
    100     int length = (w2 & 0xffffff);
    101 
    102     if (control) {
    103       int version = (w1 & 0x7fff0000) >>> 16;
    104       int type = (w1 & 0xffff);
    105 
    106       if (version != 3) {
    107         throw new ProtocolException("version != 3: " + version);
    108       }
    109 
    110       switch (type) {
    111         case SpdyConnection.TYPE_SYN_STREAM:
    112           readSynStream(handler, flags, length);
    113           return true;
    114 
    115         case SpdyConnection.TYPE_SYN_REPLY:
    116           readSynReply(handler, flags, length);
    117           return true;
    118 
    119         case SpdyConnection.TYPE_RST_STREAM:
    120           readRstStream(handler, flags, length);
    121           return true;
    122 
    123         case SpdyConnection.TYPE_SETTINGS:
    124           readSettings(handler, flags, length);
    125           return true;
    126 
    127         case SpdyConnection.TYPE_NOOP:
    128           if (length != 0) throw ioException("TYPE_NOOP length: %d != 0", length);
    129           handler.noop();
    130           return true;
    131 
    132         case SpdyConnection.TYPE_PING:
    133           readPing(handler, flags, length);
    134           return true;
    135 
    136         case SpdyConnection.TYPE_GOAWAY:
    137           readGoAway(handler, flags, length);
    138           return true;
    139 
    140         case SpdyConnection.TYPE_HEADERS:
    141           readHeaders(handler, flags, length);
    142           return true;
    143 
    144         case SpdyConnection.TYPE_WINDOW_UPDATE:
    145           readWindowUpdate(handler, flags, length);
    146           return true;
    147 
    148         case SpdyConnection.TYPE_CREDENTIAL:
    149           Util.skipByReading(in, length);
    150           throw new UnsupportedOperationException("TODO"); // TODO: implement
    151 
    152         default:
    153           throw new IOException("Unexpected frame");
    154       }
    155     } else {
    156       int streamId = w1 & 0x7fffffff;
    157       handler.data(flags, streamId, in, length);
    158       return true;
    159     }
    160   }
    161 
    162   private void readSynStream(Handler handler, int flags, int length) throws IOException {
    163     int w1 = in.readInt();
    164     int w2 = in.readInt();
    165     int s3 = in.readShort();
    166     int streamId = w1 & 0x7fffffff;
    167     int associatedStreamId = w2 & 0x7fffffff;
    168     int priority = (s3 & 0xe000) >>> 13;
    169     int slot = s3 & 0xff;
    170     List<String> nameValueBlock = readNameValueBlock(length - 10);
    171     handler.synStream(flags, streamId, associatedStreamId, priority, slot, nameValueBlock);
    172   }
    173 
    174   private void readSynReply(Handler handler, int flags, int length) throws IOException {
    175     int w1 = in.readInt();
    176     int streamId = w1 & 0x7fffffff;
    177     List<String> nameValueBlock = readNameValueBlock(length - 4);
    178     handler.synReply(flags, streamId, nameValueBlock);
    179   }
    180 
    181   private void readRstStream(Handler handler, int flags, int length) throws IOException {
    182     if (length != 8) throw ioException("TYPE_RST_STREAM length: %d != 8", length);
    183     int streamId = in.readInt() & 0x7fffffff;
    184     int statusCode = in.readInt();
    185     handler.rstStream(flags, streamId, statusCode);
    186   }
    187 
    188   private void readHeaders(Handler handler, int flags, int length) throws IOException {
    189     int w1 = in.readInt();
    190     int streamId = w1 & 0x7fffffff;
    191     List<String> nameValueBlock = readNameValueBlock(length - 4);
    192     handler.headers(flags, streamId, nameValueBlock);
    193   }
    194 
    195   private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
    196     if (length != 8) throw ioException("TYPE_WINDOW_UPDATE length: %d != 8", length);
    197     int w1 = in.readInt();
    198     int w2 = in.readInt();
    199     int streamId = w1 & 0x7fffffff;
    200     int deltaWindowSize = w2 & 0x7fffffff;
    201     handler.windowUpdate(flags, streamId, deltaWindowSize);
    202   }
    203 
    204   private DataInputStream newNameValueBlockStream() {
    205     // Limit the inflater input stream to only those bytes in the Name/Value block.
    206     final InputStream throttleStream = new InputStream() {
    207       @Override public int read() throws IOException {
    208         return Util.readSingleByte(this);
    209       }
    210 
    211       @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException {
    212         byteCount = Math.min(byteCount, compressedLimit);
    213         int consumed = in.read(buffer, offset, byteCount);
    214         compressedLimit -= consumed;
    215         return consumed;
    216       }
    217 
    218       @Override public void close() throws IOException {
    219         in.close();
    220       }
    221     };
    222 
    223     // Subclass inflater to install a dictionary when it's needed.
    224     Inflater inflater = new Inflater() {
    225       @Override public int inflate(byte[] buffer, int offset, int count)
    226           throws DataFormatException {
    227         int result = super.inflate(buffer, offset, count);
    228         if (result == 0 && needsDictionary()) {
    229           setDictionary(DICTIONARY);
    230           result = super.inflate(buffer, offset, count);
    231         }
    232         return result;
    233       }
    234     };
    235 
    236     return new DataInputStream(new InflaterInputStream(throttleStream, inflater));
    237   }
    238 
    239   private List<String> readNameValueBlock(int length) throws IOException {
    240     this.compressedLimit += length;
    241     try {
    242       int numberOfPairs = nameValueBlockIn.readInt();
    243       if (numberOfPairs < 0) {
    244         Logger.getLogger(getClass().getName()).warning("numberOfPairs < 0: " + numberOfPairs);
    245         throw ioException("numberOfPairs < 0");
    246       }
    247       List<String> entries = new ArrayList<String>(numberOfPairs * 2);
    248       for (int i = 0; i < numberOfPairs; i++) {
    249         String name = readString();
    250         String values = readString();
    251         if (name.length() == 0) throw ioException("name.length == 0");
    252         if (values.length() == 0) throw ioException("values.length == 0");
    253         entries.add(name);
    254         entries.add(values);
    255       }
    256 
    257       if (compressedLimit != 0) {
    258         Logger.getLogger(getClass().getName()).warning("compressedLimit > 0: " + compressedLimit);
    259       }
    260 
    261       return entries;
    262     } catch (DataFormatException e) {
    263       throw new IOException(e.getMessage());
    264     }
    265   }
    266 
    267   private String readString() throws DataFormatException, IOException {
    268     int length = nameValueBlockIn.readInt();
    269     byte[] bytes = new byte[length];
    270     Util.readFully(nameValueBlockIn, bytes);
    271     return new String(bytes, 0, length, "UTF-8");
    272   }
    273 
    274   private void readPing(Handler handler, int flags, int length) throws IOException {
    275     if (length != 4) throw ioException("TYPE_PING length: %d != 4", length);
    276     int id = in.readInt();
    277     handler.ping(flags, id);
    278   }
    279 
    280   private void readGoAway(Handler handler, int flags, int length) throws IOException {
    281     if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
    282     int lastGoodStreamId = in.readInt() & 0x7fffffff;
    283     int statusCode = in.readInt();
    284     handler.goAway(flags, lastGoodStreamId, statusCode);
    285   }
    286 
    287   private void readSettings(Handler handler, int flags, int length) throws IOException {
    288     int numberOfEntries = in.readInt();
    289     if (length != 4 + 8 * numberOfEntries) {
    290       throw ioException("TYPE_SETTINGS length: %d != 4 + 8 * %d", length, numberOfEntries);
    291     }
    292     Settings settings = new Settings();
    293     for (int i = 0; i < numberOfEntries; i++) {
    294       int w1 = in.readInt();
    295       int value = in.readInt();
    296       int idFlags = (w1 & 0xff000000) >>> 24;
    297       int id = w1 & 0xffffff;
    298       settings.set(id, idFlags, value);
    299     }
    300     handler.settings(flags, settings);
    301   }
    302 
    303   private static IOException ioException(String message, Object... args) throws IOException {
    304     throw new IOException(String.format(message, args));
    305   }
    306 
    307   @Override public void close() throws IOException {
    308     Util.closeAll(in, nameValueBlockIn);
    309   }
    310 
    311   public interface Handler {
    312     void data(int flags, int streamId, InputStream in, int length) throws IOException;
    313 
    314     void synStream(int flags, int streamId, int associatedStreamId, int priority, int slot,
    315         List<String> nameValueBlock);
    316 
    317     void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException;
    318     void headers(int flags, int streamId, List<String> nameValueBlock) throws IOException;
    319     void rstStream(int flags, int streamId, int statusCode);
    320     void settings(int flags, Settings settings);
    321     void noop();
    322     void ping(int flags, int streamId);
    323     void goAway(int flags, int lastGoodStreamId, int statusCode);
    324     void windowUpdate(int flags, int streamId, int deltaWindowSize);
    325   }
    326 }
    327