diff options
author | Koushik Dutta <koushd@gmail.com> | 2014-07-28 13:56:37 -0700 |
---|---|---|
committer | Koushik Dutta <koushd@gmail.com> | 2014-07-28 13:56:37 -0700 |
commit | 9ec1a8ded7e3b59da7c2b53ebe0e4cf4ece566e1 (patch) | |
tree | 7c1202c625d2ab82f57b3475fa7b5618c8268383 /AndroidAsync | |
parent | 0e28ad9a43f799df307e7f0972d48e4c1108b4b9 (diff) | |
download | AndroidAsync-9ec1a8ded7e3b59da7c2b53ebe0e4cf4ece566e1.tar.gz AndroidAsync-9ec1a8ded7e3b59da7c2b53ebe0e4cf4ece566e1.tar.bz2 AndroidAsync-9ec1a8ded7e3b59da7c2b53ebe0e4cf4ece566e1.zip |
Restructure into single package, change visibility to package only.
Diffstat (limited to 'AndroidAsync')
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java | 13 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/BitArray.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/BitArray.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/ByteString.java | 285 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/ErrorCode.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/ErrorCode.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/FrameReader.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/FrameReader.java) | 5 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/FrameWriter.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/FrameWriter.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Header.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Header.java) | 6 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/HeaderReader.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HeaderReader.java) | 5 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/HeadersMode.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HeadersMode.java) | 2 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/HpackDraft08.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HpackDraft08.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Http20Draft13.java | 764 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Huffman.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Huffman.java) | 2 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Ping.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Ping.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Settings.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Settings.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Spdy3.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Spdy3.java) | 5 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java | 1 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyTransport.java | 1 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Util.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/Util.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/Variant.java (renamed from AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Variant.java) | 4 | ||||
-rw-r--r-- | AndroidAsync/test/src/com/koushikdutta/async/test/ConscryptTests.java | 12 | ||||
-rw-r--r-- | AndroidAsync/test/src/com/koushikdutta/async/test/Handshake.java | 2 |
21 files changed, 1082 insertions, 53 deletions
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java index 45117dc..aca08f2 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java @@ -10,17 +10,6 @@ import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.callback.WritableCallback; import com.koushikdutta.async.future.SimpleFuture; import com.koushikdutta.async.http.Protocol; -import com.koushikdutta.async.http.spdy.okhttp.internal.ByteString; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.ErrorCode; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.FrameReader; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.FrameWriter; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Header; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.HeadersMode; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Http20Draft13; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Ping; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Settings; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Spdy3; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Variant; import java.io.IOException; import java.util.Hashtable; @@ -28,7 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import static com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE; +import static com.koushikdutta.async.http.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE; /** * Created by koush on 7/16/14. diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/BitArray.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/BitArray.java index 5db2b6e..1aa55b0 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/BitArray.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/BitArray.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal; +package com.koushikdutta.async.http.spdy; import java.util.ArrayList; import java.util.Arrays; @@ -22,7 +22,7 @@ import java.util.List; import static java.lang.String.format; /** A simple bitset which supports left shifting. */ -public interface BitArray { +interface BitArray { void clear(); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/ByteString.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/ByteString.java new file mode 100644 index 0000000..263b41b --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/ByteString.java @@ -0,0 +1,285 @@ +/* + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.koushikdutta.async.http.spdy; + +import android.util.Base64; + +import com.koushikdutta.async.util.Charsets; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * An immutable sequence of bytes. + * + * <p><strong>Full disclosure:</strong> this class provides untrusted input and + * output streams with raw access to the underlying byte array. A hostile + * stream implementation could keep a reference to the mutable byte string, + * violating the immutable guarantee of this class. For this reason a byte + * string's immutability guarantee cannot be relied upon for security in applets + * and other environments that run both trusted and untrusted code in the same + * process. + */ +final class ByteString implements Serializable { + private static final char[] HEX_DIGITS = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private static final long serialVersionUID = 1L; + + /** A singleton empty {@code ByteString}. */ + public static final ByteString EMPTY = ByteString.of(); + + final byte[] data; + private transient int hashCode; // Lazily computed; 0 if unknown. + private transient String utf8; // Lazily computed. + + ByteString(byte[] data) { + this.data = data; // Trusted internal constructor doesn't clone data. + } + + /** + * Returns a new byte string containing a clone of the bytes of {@code data}. + */ + public static ByteString of(byte... data) { + if (data == null) throw new IllegalArgumentException("data == null"); + return new ByteString(data.clone()); + } + + /** + * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting + * at {@code offset}. + */ + public static ByteString of(byte[] data, int offset, int byteCount) { + if (data == null) throw new IllegalArgumentException("data == null"); + Util.checkOffsetAndCount(data.length, offset, byteCount); + + byte[] copy = new byte[byteCount]; + System.arraycopy(data, offset, copy, 0, byteCount); + return new ByteString(copy); + } + + /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */ + public static ByteString encodeUtf8(String s) { + if (s == null) throw new IllegalArgumentException("s == null"); + ByteString byteString = new ByteString(s.getBytes(Charsets.UTF_8)); + byteString.utf8 = s; + return byteString; + } + + /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */ + public String utf8() { + String result = utf8; + // We don't care if we double-allocate in racy code. + return result != null ? result : (utf8 = new String(data, Charsets.UTF_8)); + } + + /** + * Returns this byte string encoded as <a + * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the + * RFC, the returned string does not wrap lines at 76 columns. + */ + public String base64() { + return Base64.encodeToString(data, Base64.DEFAULT); + } + + /** + * Decodes the Base64-encoded bytes and returns their value as a byte string. + * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. + */ + public static ByteString decodeBase64(String base64) { + if (base64 == null) throw new IllegalArgumentException("base64 == null"); + byte[] decoded = Base64.decode(base64, Base64.DEFAULT); + return decoded != null ? new ByteString(decoded) : null; + } + + /** Returns this byte string encoded in hexadecimal. */ + public String hex() { + char[] result = new char[data.length * 2]; + int c = 0; + for (byte b : data) { + result[c++] = HEX_DIGITS[(b >> 4) & 0xf]; + result[c++] = HEX_DIGITS[b & 0xf]; + } + return new String(result); + } + + /** Decodes the hex-encoded bytes and returns their value a byte string. */ + public static ByteString decodeHex(String hex) { + if (hex == null) throw new IllegalArgumentException("hex == null"); + if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex); + + byte[] result = new byte[hex.length() / 2]; + for (int i = 0; i < result.length; i++) { + int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4; + int d2 = decodeHexDigit(hex.charAt(i * 2 + 1)); + result[i] = (byte) (d1 + d2); + } + return of(result); + } + + private static int decodeHexDigit(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + throw new IllegalArgumentException("Unexpected hex digit: " + c); + } + + /** + * Reads {@code count} bytes from {@code in} and returns the result. + * + * @throws java.io.EOFException if {@code in} has fewer than {@code count} + * bytes to read. + */ + public static ByteString read(InputStream in, int byteCount) throws IOException { + if (in == null) throw new IllegalArgumentException("in == null"); + if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); + + byte[] result = new byte[byteCount]; + for (int offset = 0, read; offset < byteCount; offset += read) { + read = in.read(result, offset, byteCount - offset); + if (read == -1) throw new EOFException(); + } + return new ByteString(result); + } + + /** + * Returns a byte string equal to this byte string, but with the bytes 'A' + * through 'Z' replaced with the corresponding byte in 'a' through 'z'. + * Returns this byte string if it contains no bytes in 'A' through 'Z'. + */ + public ByteString toAsciiLowercase() { + // Search for an uppercase character. If we don't find one, return this. + for (int i = 0; i < data.length; i++) { + byte c = data[i]; + if (c < 'A' || c > 'Z') continue; + + // If we reach this point, this string is not not lowercase. Create and + // return a new byte string. + byte[] lowercase = data.clone(); + lowercase[i++] = (byte) (c - ('A' - 'a')); + for (; i < lowercase.length; i++) { + c = lowercase[i]; + if (c < 'A' || c > 'Z') continue; + lowercase[i] = (byte) (c - ('A' - 'a')); + } + return new ByteString(lowercase); + } + return this; + } + + /** + * Returns a byte string equal to this byte string, but with the bytes 'a' + * through 'z' replaced with the corresponding byte in 'A' through 'Z'. + * Returns this byte string if it contains no bytes in 'a' through 'z'. + */ + public ByteString toAsciiUppercase() { + // Search for an lowercase character. If we don't find one, return this. + for (int i = 0; i < data.length; i++) { + byte c = data[i]; + if (c < 'a' || c > 'z') continue; + + // If we reach this point, this string is not not uppercase. Create and + // return a new byte string. + byte[] lowercase = data.clone(); + lowercase[i++] = (byte) (c - ('a' - 'A')); + for (; i < lowercase.length; i++) { + c = lowercase[i]; + if (c < 'a' || c > 'z') continue; + lowercase[i] = (byte) (c - ('a' - 'A')); + } + return new ByteString(lowercase); + } + return this; + } + + /** Returns the byte at {@code pos}. */ + public byte getByte(int pos) { + return data[pos]; + } + + /** + * Returns the number of bytes in this ByteString. + */ + public int size() { + return data.length; + } + + /** + * Returns a byte array containing a copy of the bytes in this {@code ByteString}. + */ + public byte[] toByteArray() { + return data.clone(); + } + + /** Writes the contents of this byte string to {@code out}. */ + public void write(OutputStream out) throws IOException { + if (out == null) throw new IllegalArgumentException("out == null"); + out.write(data); + } + + @Override public boolean equals(Object o) { + return o == this || o instanceof ByteString && Arrays.equals(((ByteString) o).data, data); + } + + @Override public int hashCode() { + int result = hashCode; + return result != 0 ? result : (hashCode = Arrays.hashCode(data)); + } + + @Override public String toString() { + if (data.length == 0) { + return "ByteString[size=0]"; + } + + if (data.length <= 16) { + return String.format("ByteString[size=%s data=%s]", data.length, hex()); + } + + try { + return String.format("ByteString[size=%s md5=%s]", data.length, + ByteString.of(MessageDigest.getInstance("MD5").digest(data)).hex()); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(); + } + } + + private void readObject(ObjectInputStream in) throws IOException { + int dataLength = in.readInt(); + ByteString byteString = ByteString.read(in, dataLength); + try { + Field field = ByteString.class.getDeclaredField("data"); + field.setAccessible(true); + field.set(this, byteString.data); + } catch (NoSuchFieldException e) { + throw new AssertionError(); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeInt(data.length); + out.write(data); + } +} diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/ErrorCode.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/ErrorCode.java index 9a83aaa..11ed9f2 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/ErrorCode.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/ErrorCode.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; // http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-7 -public enum ErrorCode { +enum ErrorCode { /** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */ NO_ERROR(0, -1, 0), diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/FrameReader.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/FrameReader.java index 674d80c..1cbf2bf 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/FrameReader.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/FrameReader.java @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.ByteBufferList; -import com.koushikdutta.async.http.spdy.okhttp.internal.ByteString; import java.util.List; /** * Reads transport frames for SPDY/3 or HTTP/2. */ -public interface FrameReader { +interface FrameReader { // void readConnectionPreface() throws IOException; // boolean nextFrame(Handler handler) throws IOException; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/FrameWriter.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/FrameWriter.java index 5bbf060..b82a794 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/FrameWriter.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/FrameWriter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.ByteBufferList; @@ -23,7 +23,7 @@ import java.io.IOException; import java.util.List; /** Writes transport frames for SPDY/3 or HTTP/2. */ -public interface FrameWriter extends Closeable { +interface FrameWriter extends Closeable { /** HTTP/2 only. */ void connectionPreface() throws IOException; void ackSettings() throws IOException; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Header.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Header.java index bf9aaeb..610c816 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Header.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Header.java @@ -1,10 +1,8 @@ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; -import com.koushikdutta.async.http.spdy.okhttp.internal.ByteString; - /** HTTP header: the name is an ASCII string, but the value can be UTF-8. */ -public final class Header { +final class Header { // Special header names defined in the SPDY and HTTP/2 specs. public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status"); public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method"); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HeaderReader.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/HeaderReader.java index 0df4ec8..f8dbbf4 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HeaderReader.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/HeaderReader.java @@ -1,7 +1,6 @@ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.ByteBufferList; -import com.koushikdutta.async.http.spdy.okhttp.internal.ByteString; import java.io.IOException; import java.nio.ByteBuffer; @@ -14,7 +13,7 @@ import java.util.zip.Inflater; /** * Created by koush on 7/27/14. */ -public class HeaderReader { +class HeaderReader { Inflater inflater; public HeaderReader() { inflater = new Inflater() { diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HeadersMode.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/HeadersMode.java index 7ec54b5..ebb7233 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HeadersMode.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/HeadersMode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; public enum HeadersMode { SPDY_SYN_STREAM, diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HpackDraft08.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/HpackDraft08.java index 8ef4aa5..a77fce4 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/HpackDraft08.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/HpackDraft08.java @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.ByteBufferList; -import com.koushikdutta.async.http.spdy.okhttp.internal.BitArray; -import com.koushikdutta.async.http.spdy.okhttp.internal.ByteString; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/Http20Draft13.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Http20Draft13.java new file mode 100644 index 0000000..d1ec339 --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Http20Draft13.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2013 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.koushikdutta.async.http.spdy; + +import com.koushikdutta.async.BufferedDataSink; +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.DataEmitterReader; +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.http.Protocol; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.List; +import java.util.logging.Logger; + +import static com.koushikdutta.async.http.spdy.Http20Draft13.FrameLogger.formatHeader; +import static java.lang.String.format; +import static java.util.logging.Level.FINE; + +/** + * Read and write HTTP/2 v13 frames. + * <p>http://tools.ietf.org/html/draft-ietf-httpbis-http2-13 + */ + +final class Http20Draft13 implements Variant { + private static final Logger logger = Logger.getLogger(Http20Draft13.class.getName()); + + @Override + public Protocol getProtocol() { + return Protocol.HTTP_2; + } + + private static final ByteString CONNECTION_PREFACE + = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); + + static final int MAX_FRAME_SIZE = 0x3fff; // 16383 + + static final byte TYPE_DATA = 0x0; + static final byte TYPE_HEADERS = 0x1; + static final byte TYPE_PRIORITY = 0x2; + static final byte TYPE_RST_STREAM = 0x3; + static final byte TYPE_SETTINGS = 0x4; + static final byte TYPE_PUSH_PROMISE = 0x5; + static final byte TYPE_PING = 0x6; + static final byte TYPE_GOAWAY = 0x7; + static final byte TYPE_WINDOW_UPDATE = 0x8; + static final byte TYPE_CONTINUATION = 0x9; + + static final byte FLAG_NONE = 0x0; + static final byte FLAG_ACK = 0x1; // Used for settings and ping. + static final byte FLAG_END_STREAM = 0x1; // Used for headers and data. + static final byte FLAG_END_SEGMENT = 0x2; + static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation. + static final byte FLAG_END_PUSH_PROMISE = 0x4; + static final byte FLAG_PADDED = 0x8; // Used for headers and data. + static final byte FLAG_PRIORITY = 0x20; // Used for headers. + static final byte FLAG_COMPRESSED = 0x20; // Used for data. + + /** + * Creates a frame reader with max header table size of 4096 and data frame + * compression disabled. + */ + @Override + public FrameReader newReader(DataEmitter source, FrameReader.Handler handler, boolean client) { + return new Reader(source, handler, 4096, client); + } + + @Override + public FrameWriter newWriter(BufferedDataSink sink, boolean client) { + return new Writer(sink, client); + } + + @Override + public int maxFrameSize() { + return MAX_FRAME_SIZE; + } + + static final class Reader implements FrameReader { + private final DataEmitter emitter; + private final boolean client; + private final Handler handler; + private final DataEmitterReader reader; + + // Visible for testing. + final HpackDraft08.Reader hpackReader; + + Reader(DataEmitter emitter, Handler handler, int headerTableSize, boolean client) { + this.emitter = emitter; + this.client = client; + this.hpackReader = new HpackDraft08.Reader(headerTableSize); + this.handler = handler; + reader = new DataEmitterReader(); + + parseFrameHeader(); + } + + private void parseFrameHeader() { + emitter.setDataCallback(reader); + reader.read(8, onFrame); + } + + int w1; + int w2; + byte flags; + byte type; + short length; + int streamId; + private final DataCallback onFrame = new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + bb.order(ByteOrder.BIG_ENDIAN); + w1 = bb.getInt(); + w2 = bb.getInt(); + + // boolean r = (w1 & 0xc0000000) != 0; // Reserved: Ignore first 2 bits. + length = (short) ((w1 & 0x3fff0000) >> 16); // 14-bit unsigned == MAX_FRAME_SIZE + type = (byte) ((w1 & 0xff00) >> 8); + flags = (byte) (w1 & 0xff); + // boolean r = (w2 & 0x80000000) != 0; // Reserved: Ignore first bit. + streamId = (w2 & 0x7fffffff); // 31-bit opaque identifier. + if (logger.isLoggable(FINE)) + logger.fine(formatHeader(true, streamId, length, type, flags)); + + reader.read(length, onFullFrame); + } + }; + + private final DataCallback onFullFrame = new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + try { + switch (type) { + case TYPE_DATA: + readData(bb, length, flags, streamId); + break; + + case TYPE_HEADERS: + readHeaders(bb, length, flags, streamId); + break; + + case TYPE_PRIORITY: + readPriority(bb, length, flags, streamId); + break; + + case TYPE_RST_STREAM: + readRstStream(bb, length, flags, streamId); + break; + + case TYPE_SETTINGS: + readSettings(bb, length, flags, streamId); + break; + + case TYPE_PUSH_PROMISE: + readPushPromise(bb, length, flags, streamId); + break; + + case TYPE_PING: + readPing(bb, length, flags, streamId); + break; + + case TYPE_GOAWAY: + readGoAway(bb, length, flags, streamId); + break; + + case TYPE_WINDOW_UPDATE: + readWindowUpdate(bb, length, flags, streamId); + break; + + case TYPE_CONTINUATION: + readContinuation(bb, length, flags, streamId); + break; + + default: + // Implementations MUST discard frames that have unknown or unsupported types. + bb.recycle(); + } + parseFrameHeader(); + } + catch (IOException e) { + handler.error(e); + } + } + }; + + /* + @Override + public void readConnectionPreface() throws IOException { + if (client) return; // Nothing to read; servers doesn't send a connection preface! + ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size()); + if (logger.isLoggable(FINE)) + logger.fine(format("<< CONNECTION %s", connectionPreface.hex())); + if (!CONNECTION_PREFACE.equals(connectionPreface)) { + throw ioException("Expected a connection header but was %s", connectionPreface.utf8()); + } + } + */ + + byte pendingHeaderType; + private void readHeaders(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0"); + + + short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.get() & 0xff) : 0; + + if ((flags & FLAG_PRIORITY) != 0) { + readPriority(source, streamId); + length -= 5; // account for above read. + } + + length = lengthWithoutPadding(length, flags, padding); + + pendingHeaderType = type; + readHeaderBlock(source, length, padding, flags, streamId); + +// handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS); + } + + private void readContinuation(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (streamId != continuingStreamId) + throw new IOException("continuation stream id mismatch"); + readHeaderBlock(source, length, (short)0, flags, streamId); + } + + int continuingStreamId; + private void readHeaderBlock(ByteBufferList source, short length, short padding, byte flags, int streamId) + throws IOException { + source.skip(padding); + hpackReader.refill(source); + hpackReader.readHeaders(); + hpackReader.emitReferenceSet(); + // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20. + // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3 + if ((flags & FLAG_END_HEADERS) != 0) { + if (pendingHeaderType == TYPE_HEADERS) { + boolean endStream = (flags & FLAG_END_STREAM) != 0; + handler.headers(false, endStream, streamId, -1, hpackReader.getAndReset(), HeadersMode.HTTP_20_HEADERS); + } + else if (pendingHeaderType == TYPE_PUSH_PROMISE) { + handler.pushPromise(streamId, promisedStreamId, hpackReader.getAndReset()); + } + else { + throw new AssertionError("unknown header type"); + } + } + else { + continuingStreamId = streamId; + } + } + + private void readData(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED + boolean inFinished = (flags & FLAG_END_STREAM) != 0; + boolean gzipped = (flags & FLAG_COMPRESSED) != 0; + if (gzipped) { + throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA"); + } + + short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.get() & 0xff) : 0; + length = lengthWithoutPadding(length, flags, padding); + + handler.data(inFinished, streamId, source); + source.skip(padding); + } + + private void readPriority(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length); + if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0"); + readPriority(source, streamId); + } + + private void readPriority(ByteBufferList source, int streamId) throws IOException { + int w1 = source.getInt(); + boolean exclusive = (w1 & 0x80000000) != 0; + int streamDependency = (w1 & 0x7fffffff); + int weight = (source.get() & 0xff) + 1; + handler.priority(streamId, streamDependency, weight, exclusive); + } + + private void readRstStream(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length); + if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0"); + int errorCodeInt = source.getInt(); + ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); + if (errorCode == null) { + throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt); + } + handler.rstStream(streamId, errorCode); + } + + private void readSettings(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0"); + if ((flags & FLAG_ACK) != 0) { + if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!"); + handler.ackSettings(); + return; + } + + if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length); + Settings settings = new Settings(); + for (int i = 0; i < length; i += 6) { + short id = source.getShort(); + int value = source.getInt(); + + switch (id) { + case 1: // SETTINGS_HEADER_TABLE_SIZE + break; + case 2: // SETTINGS_ENABLE_PUSH + if (value != 0 && value != 1) { + throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1"); + } + break; + case 3: // SETTINGS_MAX_CONCURRENT_STREAMS + id = 4; // Renumbered in draft 10. + break; + case 4: // SETTINGS_INITIAL_WINDOW_SIZE + id = 7; // Renumbered in draft 10. + if (value < 0) { + throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1"); + } + break; + case 5: // SETTINGS_COMPRESS_DATA + break; + default: + throw ioException("PROTOCOL_ERROR invalid settings id: %s", id); + } + settings.set(id, 0, value); + } + handler.settings(false, settings); + if (settings.getHeaderTableSize() >= 0) { + hpackReader.maxHeaderTableByteCountSetting(settings.getHeaderTableSize()); + } + } + + int promisedStreamId; + private void readPushPromise(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (streamId == 0) { + throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0"); + } + short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.get() & 0xff) : 0; + promisedStreamId = source.getInt() & 0x7fffffff; + length -= 4; // account for above read. + length = lengthWithoutPadding(length, flags, padding); + pendingHeaderType = TYPE_PUSH_PROMISE; + readHeaderBlock(source, length, padding, flags, streamId); + } + + private void readPing(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (length != 8) throw ioException("TYPE_PING length != 8: %s", length); + if (streamId != 0) throw ioException("TYPE_PING streamId != 0"); + int payload1 = source.getInt(); + int payload2 = source.getInt(); + boolean ack = (flags & FLAG_ACK) != 0; + handler.ping(ack, payload1, payload2); + } + + private void readGoAway(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length); + if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0"); + int lastStreamId = source.getInt(); + int errorCodeInt = source.getInt(); + int opaqueDataLength = length - 8; + ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt); + if (errorCode == null) { + throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt); + } + ByteString debugData = ByteString.EMPTY; + if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection. + debugData = ByteString.of(source.getBytes(opaqueDataLength)); + } + handler.goAway(lastStreamId, errorCode, debugData); + } + + private void readWindowUpdate(ByteBufferList source, short length, byte flags, int streamId) + throws IOException { + if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length); + long increment = (source.getInt() & 0x7fffffffL); + if (increment == 0) throw ioException("windowSizeIncrement was 0", increment); + handler.windowUpdate(streamId, increment); + } + } + + static final class Writer implements FrameWriter { + private final BufferedDataSink sink; + private final boolean client; + private final HpackDraft08.Writer hpackWriter; + private boolean closed; + private final ByteBufferList frameHeader = new ByteBufferList(); + + Writer(BufferedDataSink sink, boolean client) { + this.sink = sink; + this.client = client; + this.hpackWriter = new HpackDraft08.Writer(); + } + + @Override + public synchronized void ackSettings() throws IOException { + if (closed) throw new IOException("closed"); + int length = 0; + byte type = TYPE_SETTINGS; + byte flags = FLAG_ACK; + int streamId = 0; + frameHeader(streamId, length, type, flags); + } + + @Override + public synchronized void connectionPreface() throws IOException { + if (closed) throw new IOException("closed"); + if (!client) return; // Nothing to write; servers don't send connection headers! + if (logger.isLoggable(FINE)) { + logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex())); + } + sink.write(new ByteBufferList(CONNECTION_PREFACE.toByteArray())); + } + + @Override + public synchronized void synStream(boolean outFinished, boolean inFinished, + int streamId, int associatedStreamId, List<Header> headerBlock) + throws IOException { + if (inFinished) throw new UnsupportedOperationException(); + if (closed) throw new IOException("closed"); + headers(outFinished, streamId, headerBlock); + } + + @Override + public synchronized void synReply(boolean outFinished, int streamId, + List<Header> headerBlock) throws IOException { + if (closed) throw new IOException("closed"); + headers(outFinished, streamId, headerBlock); + } + + @Override + public synchronized void headers(int streamId, List<Header> headerBlock) + throws IOException { + if (closed) throw new IOException("closed"); + headers(false, streamId, headerBlock); + } + + @Override + public synchronized void pushPromise(int streamId, int promisedStreamId, + List<Header> requestHeaders) throws IOException { + if (closed) throw new IOException("closed"); + ByteBufferList hpackBuffer = hpackWriter.writeHeaders(requestHeaders); + + long byteCount = hpackBuffer.remaining(); + int length = (int) Math.min(MAX_FRAME_SIZE - 4, byteCount); + byte type = TYPE_PUSH_PROMISE; + byte flags = byteCount == length ? FLAG_END_HEADERS : 0; + frameHeader(streamId, length + 4, type, flags); + ByteBuffer sink = ByteBufferList.obtain(8192).order(ByteOrder.BIG_ENDIAN); + sink.putInt(promisedStreamId & 0x7fffffff); + sink.flip(); + frameHeader.add(sink); + hpackBuffer.get(frameHeader, length); + this.sink.write(frameHeader); + + if (byteCount > length) writeContinuationFrames(hpackBuffer, streamId); + } + + void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException { + if (closed) throw new IOException("closed"); + ByteBufferList hpackBuffer = hpackWriter.writeHeaders(headerBlock); + + long byteCount = hpackBuffer.remaining(); + int length = (int) Math.min(MAX_FRAME_SIZE, byteCount); + byte type = TYPE_HEADERS; + byte flags = byteCount == length ? FLAG_END_HEADERS : 0; + if (outFinished) flags |= FLAG_END_STREAM; + frameHeader(streamId, length, type, flags); + hpackBuffer.get(frameHeader, length); + this.sink.write(frameHeader); + + if (byteCount > length) writeContinuationFrames(hpackBuffer, streamId); + } + + private void writeContinuationFrames(ByteBufferList hpackBuffer, int streamId) throws IOException { + while (hpackBuffer.hasRemaining()) { + int length = (int) Math.min(MAX_FRAME_SIZE, hpackBuffer.remaining()); + int newRemaining = hpackBuffer.remaining() - length; + frameHeader(streamId, length, TYPE_CONTINUATION, newRemaining == 0 ? FLAG_END_HEADERS : 0); + hpackBuffer.get(frameHeader, length); + sink.write(frameHeader); + } + } + + @Override + public synchronized void rstStream(int streamId, ErrorCode errorCode) + throws IOException { + if (closed) throw new IOException("closed"); + if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException(); + + int length = 4; + byte type = TYPE_RST_STREAM; + byte flags = FLAG_NONE; + frameHeader(streamId, length, type, flags); + ByteBuffer sink = ByteBufferList.obtain(8192).order(ByteOrder.BIG_ENDIAN); + sink.putInt(errorCode.httpCode); + sink.flip(); + this.sink.write(frameHeader.add(sink)); + } + + @Override + public synchronized void data(boolean outFinished, int streamId, ByteBufferList source) + throws IOException { + if (closed) throw new IOException("closed"); + byte flags = FLAG_NONE; + if (outFinished) flags |= FLAG_END_STREAM; + dataFrame(streamId, flags, source); + } + + void dataFrame(int streamId, byte flags, ByteBufferList buffer) throws IOException { + byte type = TYPE_DATA; + frameHeader(streamId, buffer.remaining(), type, flags); + sink.write(buffer); + } + + @Override + public synchronized void settings(Settings settings) throws IOException { + if (closed) throw new IOException("closed"); + int length = settings.size() * 6; + byte type = TYPE_SETTINGS; + byte flags = FLAG_NONE; + int streamId = 0; + frameHeader(streamId, length, type, flags); + ByteBuffer sink = ByteBufferList.obtain(8192).order(ByteOrder.BIG_ENDIAN); + for (int i = 0; i < Settings.COUNT; i++) { + if (!settings.isSet(i)) continue; + int id = i; + if (id == 4) id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered. + else if (id == 7) id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered. + sink.putShort((short) id); + sink.putInt(settings.get(i)); + } + sink.flip(); + this.sink.write(frameHeader.add(sink)); + } + + @Override + public synchronized void ping(boolean ack, int payload1, int payload2) + throws IOException { + if (closed) throw new IOException("closed"); + int length = 8; + byte type = TYPE_PING; + byte flags = ack ? FLAG_ACK : FLAG_NONE; + int streamId = 0; + frameHeader(streamId, length, type, flags); + ByteBuffer sink = ByteBufferList.obtain(256).order(ByteOrder.BIG_ENDIAN); + sink.putInt(payload1); + sink.putInt(payload2); + sink.flip(); + this.sink.write(frameHeader.add(sink)); + } + + @Override + public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, + byte[] debugData) throws IOException { + if (closed) throw new IOException("closed"); + if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1"); + int length = 8 + debugData.length; + byte type = TYPE_GOAWAY; + byte flags = FLAG_NONE; + int streamId = 0; + frameHeader(streamId, length, type, flags); + ByteBuffer sink = ByteBufferList.obtain(256).order(ByteOrder.BIG_ENDIAN); + sink.putInt(lastGoodStreamId); + sink.putInt(errorCode.httpCode); + sink.put(debugData); + sink.flip(); + this.sink.write(frameHeader.add(sink)); + } + + @Override + public synchronized void windowUpdate(int streamId, long windowSizeIncrement) + throws IOException { + if (closed) throw new IOException("closed"); + if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) { + throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s", + windowSizeIncrement); + } + int length = 4; + byte type = TYPE_WINDOW_UPDATE; + byte flags = FLAG_NONE; + frameHeader(streamId, length, type, flags); + ByteBuffer sink = ByteBufferList.obtain(256).order(ByteOrder.BIG_ENDIAN); + sink.putInt((int) windowSizeIncrement); + sink.flip(); + this.sink.write(frameHeader.add(sink)); + } + + @Override + public synchronized void close() throws IOException { + closed = true; + } + + void frameHeader(int streamId, int length, byte type, byte flags) throws IOException { + if (logger.isLoggable(FINE)) + logger.fine(formatHeader(false, streamId, length, type, flags)); + if (length > MAX_FRAME_SIZE) { + throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", MAX_FRAME_SIZE, length); + } + if ((streamId & 0x80000000) != 0) + throw illegalArgument("reserved bit set: %s", streamId); + ByteBuffer sink = ByteBufferList.obtain(256).order(ByteOrder.BIG_ENDIAN); + sink.putInt((length & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff)); + sink.putInt(streamId & 0x7fffffff); + sink.flip(); + this.sink.write(frameHeader.add(sink)); + } + } + + private static IllegalArgumentException illegalArgument(String message, Object... args) { + throw new IllegalArgumentException(format(message, args)); + } + + private static IOException ioException(String message, Object... args) throws IOException { + throw new IOException(format(message, args)); + } + + private static short lengthWithoutPadding(short length, byte flags, short padding) + throws IOException { + if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length. + if (padding > length) { + throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length); + } + return (short) (length - padding); + } + + /** + * Logs a human-readable representation of HTTP/2 frame headers. + * <p/> + * <p>The format is: + * <p/> + * <pre> + * direction streamID length type flags + * </pre> + * Where direction is {@code <<} for inbound and {@code >>} for outbound. + * <p/> + * <p> For example, the following would indicate a HEAD request sent from + * the client. + * <pre> + * {@code + * << 0x0000000f 12 HEADERS END_HEADERS|END_STREAM + * } + * </pre> + */ + static final class FrameLogger { + + static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) { + String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type); + String formattedFlags = formatFlags(type, flags); + return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length, + formattedType, formattedFlags); + } + + /** + * Looks up valid string representing flags from the table. Invalid + * combinations are represented in binary. + */ + // Visible for testing. + static String formatFlags(byte type, byte flags) { + if (flags == 0) return ""; + switch (type) { // Special case types that have 0 or 1 flag. + case TYPE_SETTINGS: + case TYPE_PING: + return flags == FLAG_ACK ? "ACK" : BINARY[flags]; + case TYPE_PRIORITY: + case TYPE_RST_STREAM: + case TYPE_GOAWAY: + case TYPE_WINDOW_UPDATE: + return BINARY[flags]; + } + String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags]; + // Special case types that have overlap flag values. + if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) { + return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation. + } else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) { + return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation. + } + return result; + } + + /** + * Lookup table for valid frame types. + */ + private static final String[] TYPES = new String[]{ + "DATA", + "HEADERS", + "PRIORITY", + "RST_STREAM", + "SETTINGS", + "PUSH_PROMISE", + "PING", + "GOAWAY", + "WINDOW_UPDATE", + "CONTINUATION" + }; + + /** + * Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid + * combinations are represented in binary. + */ + private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20. + private static final String[] BINARY = new String[256]; + + static { + for (int i = 0; i < BINARY.length; i++) { + BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0'); + } + + FLAGS[FLAG_NONE] = ""; + FLAGS[FLAG_END_STREAM] = "END_STREAM"; + FLAGS[FLAG_END_SEGMENT] = "END_SEGMENT"; + FLAGS[FLAG_END_STREAM | FLAG_END_SEGMENT] = "END_STREAM|END_SEGMENT"; + int[] prefixFlags = + new int[]{FLAG_END_STREAM, FLAG_END_SEGMENT, FLAG_END_SEGMENT | FLAG_END_STREAM}; + + FLAGS[FLAG_PADDED] = "PADDED"; + for (int prefixFlag : prefixFlags) { + FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED"; + } + + FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE. + FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED. + FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS. + int[] frameFlags = + new int[]{FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY}; + + for (int frameFlag : frameFlags) { + for (int prefixFlag : prefixFlags) { + FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag]; + FLAGS[prefixFlag | frameFlag | FLAG_PADDED] = + FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED"; + } + } + + for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation. + if (FLAGS[i] == null) FLAGS[i] = BINARY[i]; + } + } + } +} diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Huffman.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Huffman.java index dfa7153..2472a03 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Huffman.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Huffman.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Ping.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Ping.java index 0a82b43..9f5ae8f 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Ping.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Ping.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit; /** * A locally-originated ping. */ -public final class Ping { +final class Ping { private final CountDownLatch latch = new CountDownLatch(1); private long sent = -1; private long received = -1; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Settings.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Settings.java index 1b96f64..726816f 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Settings.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Settings.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import java.util.Arrays; @@ -21,7 +21,7 @@ import java.util.Arrays; * Settings describe characteristics of the sending peer, which are used by the receiving peer. * Settings are {@link com.koushikdutta.async.http.spdy.okhttp.internal.spdy.SpdyConnection connection} scoped. */ -public final class Settings { +final class Settings { /** * From the SPDY/3 and HTTP/2 specs, the default initial window size for all * streams is 64 KiB. (Chrome 25 uses 10 MiB). diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Spdy3.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Spdy3.java index 52155ca..35f4e6e 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Spdy3.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Spdy3.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.BufferedDataSink; import com.koushikdutta.async.ByteBufferList; @@ -21,7 +21,6 @@ import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.DataEmitterReader; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.http.Protocol; -import com.koushikdutta.async.http.spdy.okhttp.internal.ByteString; import com.koushikdutta.async.util.Charsets; import java.io.IOException; @@ -37,7 +36,7 @@ import java.util.zip.Deflater; * Read and write spdy/3.1 frames. * http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1 */ -public final class Spdy3 implements Variant { +final class Spdy3 implements Variant { @Override public Protocol getProtocol() { diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java index b97f261..1ac43d5 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java @@ -16,7 +16,6 @@ import com.koushikdutta.async.http.AsyncSSLSocketMiddleware; import com.koushikdutta.async.http.Headers; import com.koushikdutta.async.http.Multimap; import com.koushikdutta.async.http.Protocol; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Header; import com.koushikdutta.async.util.Charsets; import java.lang.reflect.Field; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyTransport.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyTransport.java index fb29b6a..a2e3029 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyTransport.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyTransport.java @@ -18,7 +18,6 @@ package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.http.Protocol; -import com.koushikdutta.async.http.spdy.okhttp.internal.Util; import java.util.List; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/Util.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Util.java index 6c8c0e4..288ba26 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/Util.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Util.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal; +package com.koushikdutta.async.http.spdy; import java.util.ArrayList; import java.util.Arrays; @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.List; /** Junk drawer of utility methods. */ -public final class Util { +final class Util { public static void checkOffsetAndCount(long arrayLength, long offset, long count) { if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { throw new ArrayIndexOutOfBoundsException(); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Variant.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Variant.java index 00b12ff..d54a484 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/okhttp/internal/spdy/Variant.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/Variant.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.koushikdutta.async.http.spdy.okhttp.internal.spdy; +package com.koushikdutta.async.http.spdy; import com.koushikdutta.async.BufferedDataSink; @@ -21,7 +21,7 @@ import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.http.Protocol; /** A version and dialect of the framed socket protocol. */ -public interface Variant { +interface Variant { /** The protocol as selected using NPN or ALPN. */ Protocol getProtocol(); diff --git a/AndroidAsync/test/src/com/koushikdutta/async/test/ConscryptTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/ConscryptTests.java index a50d910..cd50148 100644 --- a/AndroidAsync/test/src/com/koushikdutta/async/test/ConscryptTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/ConscryptTests.java @@ -18,12 +18,12 @@ package com.koushikdutta.async.test; import com.koushikdutta.async.ByteBufferList; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.ErrorCode; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.FrameReader; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Header; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.HeadersMode; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Settings; -import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Spdy3; +import com.koushikdutta.async.http.spdy.ErrorCode; +import com.koushikdutta.async.http.spdy.FrameReader; +import com.koushikdutta.async.http.spdy.Header; +import com.koushikdutta.async.http.spdy.HeadersMode; +import com.koushikdutta.async.http.spdy.Settings; +import com.koushikdutta.async.http.spdy.Spdy3; import com.koushikdutta.async.http.spdy.okio.BufferedSource; import com.koushikdutta.async.http.spdy.okio.ByteString; import com.koushikdutta.async.http.spdy.okio.Okio; diff --git a/AndroidAsync/test/src/com/koushikdutta/async/test/Handshake.java b/AndroidAsync/test/src/com/koushikdutta/async/test/Handshake.java index e53908f..597436b 100644 --- a/AndroidAsync/test/src/com/koushikdutta/async/test/Handshake.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/Handshake.java @@ -1,6 +1,6 @@ package com.koushikdutta.async.test; -import com.koushikdutta.async.http.spdy.okhttp.internal.Util; +import com.koushikdutta.async.http.spdy.Util; import java.security.Principal; import java.security.cert.Certificate; |