diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java index 832f79024..7a0e75402 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java @@ -63,7 +63,7 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import okio.ByteString; -import okio.OkBuffers; +import okio.Okio; import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START; import static com.squareup.okhttp.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; @@ -656,7 +656,7 @@ public final class MockWebServer { } } - InputStream bodyIn = OkBuffers.buffer(stream.getSource()).inputStream(); + InputStream bodyIn = Okio.buffer(stream.getSource()).inputStream(); ByteArrayOutputStream bodyOut = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int count; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java index 6ce965f3a..f3f04fdfb 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java @@ -39,6 +39,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadFactory; +import okio.ByteString; import okio.OkBuffer; import okio.Sink; import okio.Source; @@ -57,9 +58,6 @@ public final class Util { /** A cheap and type-safe constant for the UTF-8 Charset. */ public static final Charset UTF_8 = Charset.forName("UTF-8"); - private static final char[] DIGITS = - { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - private Util() { } @@ -303,7 +301,7 @@ public final class Util { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8")); - return bytesToHexString(md5bytes); + return ByteString.of(md5bytes).hex(); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } catch (UnsupportedEncodingException e) { @@ -311,17 +309,6 @@ public final class Util { } } - private static String bytesToHexString(byte[] bytes) { - char[] digits = DIGITS; - char[] buf = new char[bytes.length * 2]; - int c = 0; - for (byte b : bytes) { - buf[c++] = digits[(b >> 4) & 0xf]; - buf[c++] = digits[b & 0xf]; - } - return new String(buf); - } - /** Returns an immutable copy of {@code list}. */ public static List immutableList(List list) { return Collections.unmodifiableList(new ArrayList(list)); diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java index 614909488..e16f038d3 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Map; import okio.BufferedSource; import okio.ByteString; -import okio.OkBuffers; +import okio.Okio; import okio.Source; /** @@ -125,7 +125,7 @@ final class HpackDraft05 { Reader(boolean client, int maxHeaderTableByteCount, Source source) { this.huffmanCodec = client ? Huffman.Codec.RESPONSE : Huffman.Codec.REQUEST; this.maxHeaderTableByteCount = maxHeaderTableByteCount; - this.source = OkBuffers.buffer(source); + this.source = Okio.buffer(source); } int maxHeaderTableByteCount() { diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java index 69deb2234..293d817a1 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java @@ -10,7 +10,7 @@ import okio.ByteString; import okio.Deadline; import okio.InflaterSource; import okio.OkBuffer; -import okio.OkBuffers; +import okio.Okio; import okio.Source; /** @@ -70,7 +70,7 @@ class NameValueBlockReader { }; this.inflaterSource = new InflaterSource(throttleSource, inflater); - this.source = OkBuffers.buffer(inflaterSource); + this.source = Okio.buffer(inflaterSource); } public List
readNameValueBlock(int length) throws IOException { diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java index f5a1620c8..3f2ac90a4 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java @@ -33,7 +33,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import okio.BufferedSource; import okio.ByteString; -import okio.OkBuffers; +import okio.Okio; /** * A socket connection to a remote peer. A connection hosts streams which can @@ -464,7 +464,7 @@ public final class SpdyConnection implements Closeable { private boolean client; public Builder(boolean client, Socket socket) throws IOException { - this("", client, OkBuffers.buffer(OkBuffers.source(socket.getInputStream())), + this("", client, Okio.buffer(Okio.source(socket.getInputStream())), socket.getOutputStream()); } diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java index 4e78b5cdb..db2774eee 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java @@ -26,17 +26,18 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ByteStringTest { @Test public void equals() throws Exception { - ByteString byteString = ByteString.of((byte) 0x0, (byte) 0x1, (byte) 0x2); + ByteString byteString = ByteString.decodeHex("000102"); assertTrue(byteString.equals(byteString)); - assertTrue(byteString.equals(ByteString.of((byte) 0x0, (byte) 0x1, (byte) 0x2))); + assertTrue(byteString.equals(ByteString.decodeHex("000102"))); assertTrue(ByteString.of().equals(ByteString.EMPTY)); assertTrue(ByteString.EMPTY.equals(ByteString.of())); assertFalse(byteString.equals(new Object())); - assertFalse(byteString.equals(ByteString.of((byte) 0x0, (byte) 0x2, (byte) 0x1))); + assertFalse(byteString.equals(ByteString.decodeHex("000201"))); } private final String bronzeHorseman = "На берегу пустынных волн"; @@ -58,15 +59,15 @@ public class ByteStringTest { } @Test public void testHashCode() throws Exception { - ByteString byteString = ByteString.of((byte) 0x1, (byte) 0x2); + ByteString byteString = ByteString.decodeHex("0102"); assertEquals(byteString.hashCode(), byteString.hashCode()); - assertEquals(byteString.hashCode(), ByteString.of((byte) 0x1, (byte) 0x2).hashCode()); + assertEquals(byteString.hashCode(), ByteString.decodeHex("0102").hashCode()); } @Test public void read() throws Exception { InputStream in = new ByteArrayInputStream("abc".getBytes(Util.UTF_8)); - assertEquals(ByteString.of((byte) 0x61, (byte) 0x62), ByteString.read(in, 2)); - assertEquals(ByteString.of((byte) 0x63), ByteString.read(in, 1)); + assertEquals(ByteString.decodeHex("6162"), ByteString.read(in, 2)); + assertEquals(ByteString.decodeHex("63"), ByteString.read(in, 1)); assertEquals(ByteString.of(), ByteString.read(in, 0)); } @@ -92,7 +93,7 @@ public class ByteStringTest { @Test public void write() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteString.of((byte) 0x61, (byte) 0x62, (byte) 0x63).write(out); + ByteString.decodeHex("616263").write(out); assertByteArraysEquals(new byte[] { 0x61, 0x62, 0x63 }, out.toByteArray()); } @@ -123,16 +124,16 @@ public class ByteStringTest { @Test public void decodeBase64() { assertEquals("", ByteString.decodeBase64("").utf8()); assertEquals(null, ByteString.decodeBase64("/===")); // Can't do anything with 6 bits! - assertEquals(bytes(0xff), ByteString.decodeBase64("//==")); - assertEquals(bytes(0xff, 0xff), ByteString.decodeBase64("///=")); - assertEquals(bytes(0xff, 0xff, 0xff), ByteString.decodeBase64("////")); - assertEquals(bytes(0xff, 0xff, 0xff, 0xff, 0xff, 0xff), ByteString.decodeBase64("////////")); + assertEquals(ByteString.decodeHex("ff"), ByteString.decodeBase64("//==")); + assertEquals(ByteString.decodeHex("ffff"), ByteString.decodeBase64("///=")); + assertEquals(ByteString.decodeHex("ffffff"), ByteString.decodeBase64("////")); + assertEquals(ByteString.decodeHex("ffffffffffff"), ByteString.decodeBase64("////////")); assertEquals("What's to be scared about? It's just a little hiccup in the power...", ByteString.decodeBase64("V2hhdCdzIHRvIGJlIHNjYXJlZCBhYm91dD8gSXQncyBqdXN0IGEgbGl0dGxlIGhpY2" + "N1cCBpbiB0aGUgcG93ZXIuLi4=").utf8()); } - @Test public void decodeWithWhitespace() { + @Test public void decodeBase64WithWhitespace() { assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64(" AA AA ").utf8()); assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64(" AA A\r\nA ").utf8()); assertEquals("\u0000\u0000\u0000", ByteString.decodeBase64("AA AA").utf8()); @@ -142,13 +143,28 @@ public class ByteStringTest { assertEquals("", ByteString.decodeBase64(" ").utf8()); } - /** Make it easy to make varargs calls. Otherwise we need a lot of (byte) casts. */ - private ByteString bytes(int... bytes) { - byte[] result = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - result[i] = (byte) bytes[i]; + @Test public void encodeHex() throws Exception { + assertEquals("000102", ByteString.of((byte) 0x0, (byte) 0x1, (byte) 0x2).hex()); + } + + @Test public void decodeHex() throws Exception { + assertEquals(ByteString.of((byte) 0x0, (byte) 0x1, (byte) 0x2), ByteString.decodeHex("000102")); + } + + @Test public void decodeHexOddNumberOfChars() throws Exception { + try { + ByteString.decodeHex("aaa"); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void decodeHexInvalidChar() throws Exception { + try { + ByteString.decodeHex("a\u0000"); + fail(); + } catch (IllegalArgumentException expected) { } - return ByteString.of(result); } private static void assertByteArraysEquals(byte[] a, byte[] b) { diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java index 49ef96ddb..e480e41b7 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java @@ -23,7 +23,7 @@ import java.io.InputStream; import java.util.Arrays; import java.util.List; import okio.ByteString; -import okio.OkBuffers; +import okio.Okio; import org.junit.Before; import org.junit.Test; @@ -800,7 +800,7 @@ public class HpackDraft05Test { } private HpackDraft05.Reader newReader(InputStream input) { - return new HpackDraft05.Reader(false, 4096, OkBuffers.source(input)); + return new HpackDraft05.Reader(false, 4096, Okio.source(input)); } private InputStream byteStream(int... bytes) { diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java index 5885a5a5e..939bb99da 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java @@ -33,7 +33,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import okio.BufferedSource; import okio.ByteString; -import okio.OkBuffers; +import okio.Okio; /** Replays prerecorded outgoing frames and records incoming frames. */ public final class MockSpdyPeer implements Closeable { @@ -118,7 +118,7 @@ public final class MockSpdyPeer implements Closeable { socket = serverSocket.accept(); OutputStream out = socket.getOutputStream(); InputStream in = socket.getInputStream(); - FrameReader reader = variant.newReader(OkBuffers.buffer(OkBuffers.source(in)), client); + FrameReader reader = variant.newReader(Okio.buffer(Okio.source(in)), client); Iterator outFramesIterator = outFrames.iterator(); byte[] outBytes = bytesOut.toByteArray(); diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java index 6152d2cfe..30994e3e5 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import okio.ByteString; import okio.OkBuffer; -import okio.OkBuffers; +import okio.Okio; import okio.Source; import org.junit.After; import org.junit.Test; @@ -1252,7 +1252,7 @@ public final class SpdyConnectionTest { assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); Source in = stream.getSource(); try { - OkBuffers.buffer(in).readByteString(101); + Okio.buffer(in).readByteString(101); fail(); } catch (IOException expected) { assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index d0cb3c97f..3e24376b3 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -34,7 +34,7 @@ import java.net.SocketTimeoutException; import javax.net.ssl.SSLSocket; import okio.BufferedSource; import okio.ByteString; -import okio.OkBuffers; +import okio.Okio; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; @@ -306,7 +306,7 @@ public final class Connection implements Closeable { * retried if the proxy requires authorization. */ private void makeTunnel(TunnelRequest tunnelRequest) throws IOException { - BufferedSource tunnelSource = OkBuffers.buffer(OkBuffers.source(in)); + BufferedSource tunnelSource = Okio.buffer(Okio.source(in)); HttpConnection tunnelConnection = new HttpConnection(pool, this, tunnelSource, out); Request request = tunnelRequest.getRequest(); String requestLine = tunnelRequest.requestLine(); @@ -338,7 +338,7 @@ public final class Connection implements Closeable { } private void streamWrapper() throws IOException { - source = OkBuffers.buffer(OkBuffers.source(in)); + source = Okio.buffer(Okio.source(in)); out = new BufferedOutputStream(out, 256); } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Job.java b/okhttp/src/main/java/com/squareup/okhttp/Job.java index be08cb99a..84cc6c51c 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Job.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Job.java @@ -25,7 +25,7 @@ import java.io.InputStream; import java.net.ProtocolException; import java.net.Proxy; import java.net.URL; -import okio.OkBuffers; +import okio.Okio; import okio.Source; import static com.squareup.okhttp.internal.Util.getEffectivePort; @@ -264,7 +264,7 @@ final class Job extends NamedRunnable { InputStream result = in; return result != null ? result - : (in = OkBuffers.buffer(source).inputStream()); + : (in = Okio.buffer(source).inputStream()); } } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Response.java b/okhttp/src/main/java/com/squareup/okhttp/Response.java index 346ef76b2..13f912420 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Response.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Response.java @@ -32,7 +32,7 @@ import java.util.Date; import java.util.List; import java.util.Set; import java.util.TreeSet; -import okio.OkBuffers; +import okio.Okio; import okio.Source; import static com.squareup.okhttp.internal.Util.UTF_8; @@ -221,7 +221,7 @@ public final class Response { // TODO: Source needs to be an API type for this to be public public Source source() { Source s = source; - return s != null ? s : (source = OkBuffers.source(byteStream())); + return s != null ? s : (source = Okio.source(byteStream())); } public final byte[] bytes() throws IOException { diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java index a20367d8c..3ba72c641 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java @@ -32,7 +32,7 @@ import java.net.Socket; import okio.BufferedSource; import okio.Deadline; import okio.OkBuffer; -import okio.OkBuffers; +import okio.Okio; import okio.Source; import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; @@ -425,7 +425,7 @@ public final class HttpConnection { /** Copy the last {@code byteCount} bytes of {@code source} to the cache body. */ protected final void cacheWrite(OkBuffer source, long byteCount) throws IOException { if (cacheBody != null) { - OkBuffers.copy(source, source.byteCount() - byteCount, byteCount, cacheBody); + Okio.copy(source, source.byteCount() - byteCount, byteCount, cacheBody); } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java index e053d8844..bffac3d2b 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java @@ -43,7 +43,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; import okio.GzipSource; -import okio.OkBuffers; +import okio.Okio; import okio.Source; import static com.squareup.okhttp.internal.Util.closeQuietly; @@ -290,7 +290,7 @@ public class HttpEngine { InputStream result = responseBodyBytes; return result != null ? result - : (responseBodyBytes = OkBuffers.buffer(getResponseBody()).inputStream()); + : (responseBodyBytes = Okio.buffer(getResponseBody()).inputStream()); } public final Connection getConnection() { diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java index 4030272d7..6c287aa8b 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java @@ -37,7 +37,7 @@ import java.util.Set; import okio.ByteString; import okio.Deadline; import okio.OkBuffer; -import okio.OkBuffers; +import okio.Okio; import okio.Source; import static com.squareup.okhttp.internal.spdy.Header.RESPONSE_STATUS; @@ -273,7 +273,7 @@ public final class SpdyTransport implements Transport { } if (cacheBody != null) { - OkBuffers.copy(sink, sink.byteCount() - read, read, cacheBody); + Okio.copy(sink, sink.byteCount() - read, read, cacheBody); } return read; diff --git a/okio/src/main/java/okio/ByteString.java b/okio/src/main/java/okio/ByteString.java index 4439e9a20..9223a8691 100644 --- a/okio/src/main/java/okio/ByteString.java +++ b/okio/src/main/java/okio/ByteString.java @@ -32,6 +32,9 @@ import java.util.Arrays; * process. */ public final class ByteString { + private static final char[] HEX_DIGITS = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + final byte[] data; private transient int hashCode; // Lazily computed; 0 if unknown. private transient String utf8; // Lazily computed. @@ -78,6 +81,37 @@ public final class ByteString { 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.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); + } + /** * Returns true when {@code ascii} is not null and equals the bytes wrapped * by this byte string. diff --git a/okio/src/main/java/okio/DeflaterSink.java b/okio/src/main/java/okio/DeflaterSink.java index b934b2d86..43a964ccd 100644 --- a/okio/src/main/java/okio/DeflaterSink.java +++ b/okio/src/main/java/okio/DeflaterSink.java @@ -40,7 +40,7 @@ public final class DeflaterSink implements Sink { private final Deflater deflater; public DeflaterSink(Sink sink, Deflater deflater) { - this.sink = OkBuffers.buffer(sink); + this.sink = Okio.buffer(sink); this.deflater = deflater; } diff --git a/okio/src/main/java/okio/GzipSource.java b/okio/src/main/java/okio/GzipSource.java index a8cf10955..26fbaaccb 100644 --- a/okio/src/main/java/okio/GzipSource.java +++ b/okio/src/main/java/okio/GzipSource.java @@ -54,7 +54,7 @@ public final class GzipSource implements Source { public GzipSource(Source source) throws IOException { this.inflater = new Inflater(true); - this.source = OkBuffers.buffer(source); + this.source = Okio.buffer(source); this.inflaterSource = new InflaterSource(this.source, inflater); } diff --git a/okio/src/main/java/okio/InflaterSource.java b/okio/src/main/java/okio/InflaterSource.java index eb65739b2..f4ee4696b 100644 --- a/okio/src/main/java/okio/InflaterSource.java +++ b/okio/src/main/java/okio/InflaterSource.java @@ -37,7 +37,7 @@ public final class InflaterSource implements Source { private boolean closed; public InflaterSource(Source source, Inflater inflater) { - this(OkBuffers.buffer(source), inflater); + this(Okio.buffer(source), inflater); } /** diff --git a/okio/src/main/java/okio/OkBuffer.java b/okio/src/main/java/okio/OkBuffer.java index 40aca0f39..10b12572e 100644 --- a/okio/src/main/java/okio/OkBuffer.java +++ b/okio/src/main/java/okio/OkBuffer.java @@ -18,6 +18,8 @@ package okio; import java.io.EOFException; import java.io.InputStream; import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -41,9 +43,6 @@ import static okio.Util.checkOffsetAndCount; * This class avoids zero-fill and GC churn by pooling byte arrays. */ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { - private static final char[] HEX_DIGITS = - { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - Segment head; long byteCount; @@ -579,22 +578,27 @@ public final class OkBuffer implements BufferedSource, BufferedSink, Cloneable { return result; } - /** - * Returns the contents of this buffer in hex. For buffers larger than 1 MiB - * this method is undefined. - */ @Override public String toString() { - if (byteCount > 0x100000) return super.toString(); - int charCount = (int) (byteCount * 2); - char[] result = new char[charCount]; - int offset = 0; - for (Segment s = head; offset < charCount; s = s.next) { - for (int i = s.pos; i < s.limit; i++) { - result[offset++] = HEX_DIGITS[(s.data[i] >> 4) & 0xf]; - result[offset++] = HEX_DIGITS[s.data[i] & 0xf]; - } + if (byteCount == 0) { + return "OkBuffer[size=0]"; + } + + if (byteCount <= 16) { + ByteString data = clone().readByteString((int) byteCount); + return String.format("OkBuffer[size=%s data=%s]", byteCount, data.hex()); + } + + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(head.data, head.pos, head.limit - head.pos); + for (Segment s = head.next; s != head; s = s.next) { + md5.update(s.data, s.pos, s.limit - s.pos); + } + return String.format("OkBuffer[size=%s md5=%s]", + byteCount, ByteString.of(md5.digest()).hex()); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(); } - return new String(result); } /** Returns a deep copy of this buffer. */ diff --git a/okio/src/main/java/okio/OkBuffers.java b/okio/src/main/java/okio/Okio.java similarity index 98% rename from okio/src/main/java/okio/OkBuffers.java rename to okio/src/main/java/okio/Okio.java index e49fe2493..afe5b53d1 100644 --- a/okio/src/main/java/okio/OkBuffers.java +++ b/okio/src/main/java/okio/Okio.java @@ -21,8 +21,8 @@ import java.io.OutputStream; import static okio.Util.checkOffsetAndCount; -public final class OkBuffers { - private OkBuffers() { +public final class Okio { + private Okio() { } public static BufferedSource buffer(Source source) { diff --git a/okio/src/test/java/okio/GzipSourceTest.java b/okio/src/test/java/okio/GzipSourceTest.java index 060d037b1..49deaa421 100644 --- a/okio/src/test/java/okio/GzipSourceTest.java +++ b/okio/src/test/java/okio/GzipSourceTest.java @@ -16,7 +16,6 @@ package okio; import java.io.IOException; -import java.util.Arrays; import java.util.zip.CRC32; import org.junit.Test; @@ -30,51 +29,52 @@ public class GzipSourceTest { @Test public void gunzip() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeader, 0, gzipHeader.length); - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(gzipHeader); + gzipped.write(deflated); + gzipped.write(gzipTrailer); assertGzipped(gzipped); } @Test public void gunzip_withHCRC() throws Exception { CRC32 hcrc = new CRC32(); - hcrc.update(gzipHeaderWithFlags((byte) 0x02)); + ByteString gzipHeader = gzipHeaderWithFlags((byte) 0x02); + hcrc.update(gzipHeader.toByteArray()); OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeaderWithFlags((byte) 0x02), 0, gzipHeader.length); + gzipped.write(gzipHeader); gzipped.writeShort(Util.reverseBytesShort((short) hcrc.getValue())); // little endian - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(deflated); + gzipped.write(gzipTrailer); assertGzipped(gzipped); } @Test public void gunzip_withExtra() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeaderWithFlags((byte) 0x04), 0, gzipHeader.length); + gzipped.write(gzipHeaderWithFlags((byte) 0x04)); gzipped.writeShort(Util.reverseBytesShort((short) 7)); // little endian extra length gzipped.write("blubber".getBytes(UTF_8), 0, 7); - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(deflated); + gzipped.write(gzipTrailer); assertGzipped(gzipped); } @Test public void gunzip_withName() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeaderWithFlags((byte) 0x08), 0, gzipHeader.length); + gzipped.write(gzipHeaderWithFlags((byte) 0x08)); gzipped.write("foo.txt".getBytes(UTF_8), 0, 7); gzipped.writeByte(0); // zero-terminated - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(deflated); + gzipped.write(gzipTrailer); assertGzipped(gzipped); } @Test public void gunzip_withComment() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeaderWithFlags((byte) 0x10), 0, gzipHeader.length); + gzipped.write(gzipHeaderWithFlags((byte) 0x10)); gzipped.write("rubbish".getBytes(UTF_8), 0, 7); gzipped.writeByte(0); // zero-terminated - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(deflated); + gzipped.write(gzipTrailer); assertGzipped(gzipped); } @@ -84,15 +84,15 @@ public class GzipSourceTest { */ @Test public void gunzip_withAll() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeaderWithFlags((byte) 0x1c), 0, gzipHeader.length); + gzipped.write(gzipHeaderWithFlags((byte) 0x1c)); gzipped.writeShort(Util.reverseBytesShort((short) 7)); // little endian extra length gzipped.write("blubber".getBytes(UTF_8), 0, 7); gzipped.write("foo.txt".getBytes(UTF_8), 0, 7); gzipped.writeByte(0); // zero-terminated gzipped.write("rubbish".getBytes(UTF_8), 0, 7); gzipped.writeByte(0); // zero-terminated - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(deflated); + gzipped.write(gzipTrailer); assertGzipped(gzipped); } @@ -108,10 +108,10 @@ public class GzipSourceTest { */ @Test public void gunzipWhenHeaderCRCIncorrect() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeaderWithFlags((byte) 0x02), 0, gzipHeader.length); + gzipped.write(gzipHeaderWithFlags((byte) 0x02)); gzipped.writeShort((short) 0); // wrong HCRC! - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, gzipTrailer.length); + gzipped.write(deflated); + gzipped.write(gzipTrailer); try { gunzip(gzipped); @@ -123,10 +123,10 @@ public class GzipSourceTest { @Test public void gunzipWhenCRCIncorrect() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeader, 0, gzipHeader.length); - gzipped.write(deflated, 0, deflated.length); + gzipped.write(gzipHeader); + gzipped.write(deflated); gzipped.writeInt(Util.reverseBytesInt(0x1234567)); // wrong CRC - gzipped.write(gzipTrailer, 3, 4); + gzipped.write(gzipTrailer.toByteArray(), 3, 4); try { gunzip(gzipped); @@ -138,9 +138,9 @@ public class GzipSourceTest { @Test public void gunzipWhenLengthIncorrect() throws Exception { OkBuffer gzipped = new OkBuffer(); - gzipped.write(gzipHeader, 0, gzipHeader.length); - gzipped.write(deflated, 0, deflated.length); - gzipped.write(gzipTrailer, 0, 4); + gzipped.write(gzipHeader); + gzipped.write(deflated); + gzipped.write(gzipTrailer.toByteArray(), 0, 4); gzipped.writeInt(Util.reverseBytesInt(0x123456)); // wrong length try { @@ -152,17 +152,11 @@ public class GzipSourceTest { } @Test public void gunzipExhaustsSource() throws Exception { - byte[] abcGzipped = { - (byte) 0x1f, (byte) 0x8b, (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x4b, (byte) 0x4c, (byte) 0x4a, (byte) 0x06, - (byte) 0x00, (byte) 0xc2, (byte) 0x41, (byte) 0x24, (byte) 0x35, (byte) 0x03, (byte) 0x00, - (byte) 0x00, (byte) 0x00 - }; - OkBuffer gzippedSource = new OkBuffer(); - gzippedSource.write(abcGzipped, 0, abcGzipped.length); + OkBuffer gzippedSource = new OkBuffer() + .write(ByteString.decodeHex("1f8b08000000000000004b4c4a0600c241243503000000")); // 'abc' ExhaustableSource exhaustableSource = new ExhaustableSource(gzippedSource); - BufferedSource gunzippedSource = OkBuffers.buffer(new GzipSource(exhaustableSource)); + BufferedSource gunzippedSource = Okio.buffer(new GzipSource(exhaustableSource)); assertEquals('a', gunzippedSource.readByte()); assertEquals('b', gunzippedSource.readByte()); @@ -173,17 +167,11 @@ public class GzipSourceTest { } @Test public void gunzipThrowsIfSourceIsNotExhausted() throws Exception { - byte[] abcGzipped = { - (byte) 0x1f, (byte) 0x8b, (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x4b, (byte) 0x4c, (byte) 0x4a, (byte) 0x06, - (byte) 0x00, (byte) 0xc2, (byte) 0x41, (byte) 0x24, (byte) 0x35, (byte) 0x03, (byte) 0x00, - (byte) 0x00, (byte) 0x00 - }; - OkBuffer gzippedSource = new OkBuffer(); - gzippedSource.write(abcGzipped, 0, abcGzipped.length); + OkBuffer gzippedSource = new OkBuffer() + .write(ByteString.decodeHex("1f8b08000000000000004b4c4a0600c241243503000000")); // 'abc' gzippedSource.writeByte('d'); // This byte shouldn't be here! - BufferedSource gunzippedSource = OkBuffers.buffer(new GzipSource(gzippedSource)); + BufferedSource gunzippedSource = Okio.buffer(new GzipSource(gzippedSource)); assertEquals('a', gunzippedSource.readByte()); assertEquals('b', gunzippedSource.readByte()); @@ -195,29 +183,22 @@ public class GzipSourceTest { } } - private byte[] gzipHeaderWithFlags(byte flags) { - byte[] result = Arrays.copyOf(gzipHeader, gzipHeader.length); + private ByteString gzipHeaderWithFlags(byte flags) { + byte[] result = gzipHeader.toByteArray(); result[3] = flags; - return result; + return ByteString.of(result); } - private final byte[] gzipHeader = new byte[] { - (byte) 0x1f, (byte) 0x8b, (byte) 0x08, 0, 0, 0, 0, 0, 0, 0 - }; + private final ByteString gzipHeader = ByteString.decodeHex("1f8b0800000000000000"); - // deflated "It's a UNIX system! I know this!" - private final byte[] deflated = new byte[] { - (byte) 0xf3, (byte) 0x2c, (byte) 0x51, (byte) 0x2f, (byte) 0x56, (byte) 0x48, (byte) 0x54, - (byte) 0x08, (byte) 0xf5, (byte) 0xf3, (byte) 0x8c, (byte) 0x50, (byte) 0x28, (byte) 0xae, - (byte) 0x2c, (byte) 0x2e, (byte) 0x49, (byte) 0xcd, (byte) 0x55, (byte) 0x54, (byte) 0xf0, - (byte) 0x54, (byte) 0xc8, (byte) 0xce, (byte) 0xcb, (byte) 0x2f, (byte) 0x57, (byte) 0x28, - (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x56, (byte) 0x04, (byte) 0x00 - }; + // Deflated "It's a UNIX system! I know this!" + private final ByteString deflated = ByteString.decodeHex( + "f32c512f56485408f5f38c5028ae2c2e49cd5554f054c8cecb2f5728c9c82c560400"); - private final byte[] gzipTrailer = new byte[] { - (byte) 0x8d, (byte) 0x8f, (byte) 0xad, (byte) 0x37, // checksum of deflated - 0x20, 0, 0, 0, // 32 in little endian - }; + private final ByteString gzipTrailer = ByteString.decodeHex("" + + "8d8fad37" // Checksum of deflated. + + "20000000" // 32 in little endian. + ); private OkBuffer gunzip(OkBuffer gzipped) throws IOException { OkBuffer result = new OkBuffer(); diff --git a/okio/src/test/java/okio/InflaterSourceTest.java b/okio/src/test/java/okio/InflaterSourceTest.java index 0795cbcd6..ba5c62de1 100644 --- a/okio/src/test/java/okio/InflaterSourceTest.java +++ b/okio/src/test/java/okio/InflaterSourceTest.java @@ -85,7 +85,7 @@ public final class InflaterSourceTest { /** Use DeflaterOutputStream to deflate source. */ private OkBuffer deflate(ByteString source) throws IOException { OkBuffer result = new OkBuffer(); - Sink sink = OkBuffers.sink(new DeflaterOutputStream(result.outputStream())); + Sink sink = Okio.sink(new DeflaterOutputStream(result.outputStream())); sink.write(new OkBuffer().write(source), source.size()); sink.close(); return result; diff --git a/okio/src/test/java/okio/OkBufferTest.java b/okio/src/test/java/okio/OkBufferTest.java index d26f72aa7..2a41fc3d0 100644 --- a/okio/src/test/java/okio/OkBufferTest.java +++ b/okio/src/test/java/okio/OkBufferTest.java @@ -15,14 +15,10 @@ */ package okio; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; import java.util.Arrays; import java.util.List; import org.junit.Test; -import static okio.Util.UTF_8; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -75,10 +71,27 @@ public final class OkBufferTest { assertEquals(repeat('a', Segment.SIZE), buffer.readUtf8(Segment.SIZE)); } - @Test public void bufferToString() throws Exception { + @Test public void toStringOnEmptyBuffer() throws Exception { OkBuffer buffer = new OkBuffer(); - buffer.writeUtf8("\u0000\u0001\u0002\u007f"); - assertEquals("0001027f", buffer.toString()); + assertEquals("OkBuffer[size=0]", buffer.toString()); + } + + @Test public void toStringOnSmallBufferIncludesContents() throws Exception { + OkBuffer buffer = new OkBuffer(); + buffer.write(ByteString.decodeHex("a1b2c3d4e5f61a2b3c4d5e6f10203040")); + assertEquals("OkBuffer[size=16 data=a1b2c3d4e5f61a2b3c4d5e6f10203040]", buffer.toString()); + } + + @Test public void toStringIncludesMd5() throws Exception { + OkBuffer buffer = new OkBuffer(); + buffer.write(ByteString.encodeUtf8("12345678901234567")); + assertEquals("OkBuffer[size=17 md5=2c9728a2138b2f25e9f89f99bdccf8db]", buffer.toString()); + } + + @Test public void toStringOnMultipleSegmentBuffer() throws Exception { + OkBuffer buffer = new OkBuffer(); + buffer.writeUtf8(repeat('a', 6144)); + assertEquals("OkBuffer[size=6144 md5=d890021f28522533c1cc1b9b1f83ce73]", buffer.toString()); } @Test public void multipleSegmentBuffers() throws Exception { @@ -325,58 +338,11 @@ public final class OkBufferTest { assertEquals(halfSegment * 4 - 1, buffer.indexOf((byte) 'd', halfSegment * 4 - 1)); } - @Test public void sinkFromOutputStream() throws Exception { - OkBuffer data = new OkBuffer(); - data.writeUtf8("a"); - data.writeUtf8(repeat('b', 9998)); - data.writeUtf8("c"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Sink sink = OkBuffers.sink(out); - sink.write(data, 3); - assertEquals("abb", out.toString("UTF-8")); - sink.write(data, data.byteCount()); - assertEquals("a" + repeat('b', 9998) + "c", out.toString("UTF-8")); - } - - @Test public void sourceFromInputStream() throws Exception { - InputStream in = new ByteArrayInputStream( - ("a" + repeat('b', Segment.SIZE * 2) + "c").getBytes(UTF_8)); - - // Source: ab...bc - Source source = OkBuffers.source(in); - OkBuffer sink = new OkBuffer(); - - // Source: b...bc. Sink: abb. - assertEquals(3, source.read(sink, 3)); - assertEquals("abb", sink.readUtf8(3)); - - // Source: b...bc. Sink: b...b. - assertEquals(Segment.SIZE, source.read(sink, 20000)); - assertEquals(repeat('b', Segment.SIZE), sink.readUtf8((int) sink.byteCount())); - - // Source: b...bc. Sink: b...bc. - assertEquals(Segment.SIZE - 1, source.read(sink, 20000)); - assertEquals(repeat('b', Segment.SIZE - 2) + "c", sink.readUtf8((int) sink.byteCount())); - - // Source and sink are empty. - assertEquals(-1, source.read(sink, 1)); - } - - @Test public void sourceFromInputStreamBounds() throws Exception { - Source source = OkBuffers.source(new ByteArrayInputStream(new byte[100])); - try { - source.read(new OkBuffer(), -1); - fail(); - } catch (IllegalArgumentException expected) { - } - } - @Test public void writeBytes() throws Exception { OkBuffer data = new OkBuffer(); data.writeByte(0xab); data.writeByte(0xcd); - assertEquals("abcd", data.toString()); + assertEquals("OkBuffer[size=2 data=abcd]", data.toString()); } @Test public void writeLastByteInSegment() throws Exception { @@ -386,21 +352,21 @@ public final class OkBufferTest { data.writeByte(0x21); assertEquals(asList(Segment.SIZE, 1), data.segmentSizes()); assertEquals(repeat('a', Segment.SIZE - 1), data.readUtf8(Segment.SIZE - 1)); - assertEquals("2021", data.toString()); + assertEquals("OkBuffer[size=2 data=2021]", data.toString()); } @Test public void writeShort() throws Exception { OkBuffer data = new OkBuffer(); data.writeShort(0xabcd); data.writeShort(0x4321); - assertEquals("abcd4321", data.toString()); + assertEquals("OkBuffer[size=4 data=abcd4321]", data.toString()); } @Test public void writeInt() throws Exception { OkBuffer data = new OkBuffer(); data.writeInt(0xabcdef01); data.writeInt(0x87654321); - assertEquals("abcdef0187654321", data.toString()); + assertEquals("OkBuffer[size=8 data=abcdef0187654321]", data.toString()); } @Test public void writeLastIntegerInSegment() throws Exception { @@ -410,7 +376,7 @@ public final class OkBufferTest { data.writeInt(0x87654321); assertEquals(asList(Segment.SIZE, 4), data.segmentSizes()); assertEquals(repeat('a', Segment.SIZE - 4), data.readUtf8(Segment.SIZE - 4)); - assertEquals("abcdef0187654321", data.toString()); + assertEquals("OkBuffer[size=8 data=abcdef0187654321]", data.toString()); } @Test public void writeIntegerDoesntQuiteFitInSegment() throws Exception { @@ -420,7 +386,7 @@ public final class OkBufferTest { data.writeInt(0x87654321); assertEquals(asList(Segment.SIZE - 3, 8), data.segmentSizes()); assertEquals(repeat('a', Segment.SIZE - 3), data.readUtf8(Segment.SIZE - 3)); - assertEquals("abcdef0187654321", data.toString()); + assertEquals("OkBuffer[size=8 data=abcdef0187654321]", data.toString()); } @Test public void readByte() throws Exception { diff --git a/okio/src/test/java/okio/OkioTest.java b/okio/src/test/java/okio/OkioTest.java new file mode 100644 index 000000000..76134325b --- /dev/null +++ b/okio/src/test/java/okio/OkioTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 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 okio; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.Arrays; +import org.junit.Test; + +import static okio.Util.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public final class OkioTest { + @Test public void sinkFromOutputStream() throws Exception { + OkBuffer data = new OkBuffer(); + data.writeUtf8("a"); + data.writeUtf8(repeat('b', 9998)); + data.writeUtf8("c"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Sink sink = Okio.sink(out); + sink.write(data, 3); + assertEquals("abb", out.toString("UTF-8")); + sink.write(data, data.byteCount()); + assertEquals("a" + repeat('b', 9998) + "c", out.toString("UTF-8")); + } + + @Test public void sourceFromInputStream() throws Exception { + InputStream in = new ByteArrayInputStream( + ("a" + repeat('b', Segment.SIZE * 2) + "c").getBytes(UTF_8)); + + // Source: ab...bc + Source source = Okio.source(in); + OkBuffer sink = new OkBuffer(); + + // Source: b...bc. Sink: abb. + assertEquals(3, source.read(sink, 3)); + assertEquals("abb", sink.readUtf8(3)); + + // Source: b...bc. Sink: b...b. + assertEquals(Segment.SIZE, source.read(sink, 20000)); + assertEquals(repeat('b', Segment.SIZE), sink.readUtf8((int) sink.byteCount())); + + // Source: b...bc. Sink: b...bc. + assertEquals(Segment.SIZE - 1, source.read(sink, 20000)); + assertEquals(repeat('b', Segment.SIZE - 2) + "c", sink.readUtf8((int) sink.byteCount())); + + // Source and sink are empty. + assertEquals(-1, source.read(sink, 1)); + } + + @Test public void sourceFromInputStreamBounds() throws Exception { + Source source = Okio.source(new ByteArrayInputStream(new byte[100])); + try { + source.read(new OkBuffer(), -1); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + private String repeat(char c, int count) { + char[] array = new char[count]; + Arrays.fill(array, c); + return new String(array); + } +}