aboutsummaryrefslogtreecommitdiffstats
path: root/AndroidAsync
diff options
context:
space:
mode:
authorKoushik Dutta <koushd@gmail.com>2014-07-28 13:56:37 -0700
committerKoushik Dutta <koushd@gmail.com>2014-07-28 13:56:37 -0700
commit9ec1a8ded7e3b59da7c2b53ebe0e4cf4ece566e1 (patch)
tree7c1202c625d2ab82f57b3475fa7b5618c8268383 /AndroidAsync
parent0e28ad9a43f799df307e7f0972d48e4c1108b4b9 (diff)
downloadAndroidAsync-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.java13
-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.java285
-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.java764
-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.java1
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyTransport.java1
-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.java12
-rw-r--r--AndroidAsync/test/src/com/koushikdutta/async/test/Handshake.java2
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;