libcore.net.spdy.SpdyStream Maven / Gradle / Ivy
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 libcore.net.spdy;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import static java.nio.ByteOrder.BIG_ENDIAN;
import java.util.List;
import libcore.io.Streams;
import libcore.util.Libcore;
/**
* A logical bidirectional stream.
*/
public final class SpdyStream {
/*
* Internal state is guarded by this. No long-running or potentially
* blocking operations are performed while the lock is held.
*/
private static final int DATA_FRAME_HEADER_LENGTH = 8;
public static final int RST_PROTOCOL_ERROR = 1;
public static final int RST_INVALID_STREAM = 2;
public static final int RST_REFUSED_STREAM = 3;
public static final int RST_UNSUPPORTED_VERSION = 4;
public static final int RST_CANCEL = 5;
public static final int RST_INTERNAL_ERROR = 6;
public static final int RST_FLOW_CONTROL_ERROR = 7;
private final int id;
private final SpdyConnection connection;
/** Headers sent by the stream initiator. Immutable and non null. */
private final List requestHeaders;
/** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */
private List responseHeaders;
private final SpdyDataInputStream in = new SpdyDataInputStream();
private final SpdyDataOutputStream out = new SpdyDataOutputStream();
/**
* The reason why this stream was abnormally closed. If there are multiple
* reasons to abnormally close this stream (such as both peers closing it
* near-simultaneously) then this is the first reason known to this peer.
*/
private int rstStatusCode = -1;
/**
* True if either side has shut down the input stream. We will receive no
* more bytes beyond those already in the buffer. Guarded by this.
*/
private boolean inFinished;
/**
* True if either side has shut down the output stream. We will write no
* more bytes to the output stream. Guarded by this.
*/
private boolean outFinished;
SpdyStream(int id, SpdyConnection connection, List requestHeaders, int flags) {
this.id = id;
this.connection = connection;
this.requestHeaders = requestHeaders;
if (isLocallyInitiated()) {
// I am the sender
inFinished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
outFinished = (flags & SpdyConnection.FLAG_FIN) != 0;
} else {
// I am the receiver
inFinished = (flags & SpdyConnection.FLAG_FIN) != 0;
outFinished = (flags & SpdyConnection.FLAG_UNIDIRECTIONAL) != 0;
}
}
/**
* Returns true if this stream was created by this peer.
*/
public boolean isLocallyInitiated() {
boolean streamIsClient = (id % 2 == 1);
return connection.isClient() == streamIsClient;
}
public SpdyConnection getConnection() {
return connection;
}
public List getRequestHeaders() {
return requestHeaders;
}
public synchronized List getResponseHeaders() throws InterruptedException {
while (responseHeaders == null && rstStatusCode == -1) {
wait();
}
return responseHeaders;
}
/**
* Returns the reason why this stream was closed, or -1 if it closed
* normally or has not yet been closed.
*/
public synchronized int getRstStatusCode() { // TODO: rename this?
return rstStatusCode;
}
public InputStream getInputStream() {
return in;
}
public OutputStream getOutputStream() {
if (!isLocallyInitiated()) {
throw new IllegalStateException("use reply for a remotely initiated stream");
}
return out;
}
/**
* Sends a reply.
*/
// TODO: support reply with FIN
public synchronized OutputStream reply(List responseHeaders) throws IOException {
if (responseHeaders == null) {
throw new NullPointerException("responseHeaders == null");
}
if (isLocallyInitiated()) {
throw new IllegalStateException("cannot reply to a locally initiated stream");
}
synchronized (this) {
if (this.responseHeaders != null) {
throw new IllegalStateException("reply already sent");
}
this.responseHeaders = responseHeaders;
}
connection.writeSynReply(id, responseHeaders);
return out;
}
/**
* Abnormally terminate this stream.
*/
public synchronized void close(int rstStatusCode) {
// TODO: no-op if inFinished == true and outFinished == true ?
if (this.rstStatusCode != -1) {
this.rstStatusCode = rstStatusCode;
inFinished = true;
outFinished = true;
connection.removeStream(id);
notifyAll();
connection.writeSynResetLater(id, rstStatusCode);
}
}
synchronized void receiveReply(List strings) throws IOException {
if (!isLocallyInitiated() || responseHeaders != null) {
throw new IOException(); // TODO: send RST
}
responseHeaders = strings;
notifyAll();
}
synchronized void receiveData(InputStream in, int flags, int length) throws IOException {
this.in.receive(in, length);
if ((flags & SpdyConnection.FLAG_FIN) != 0) {
inFinished = true;
notifyAll();
}
}
synchronized void receiveRstStream(int statusCode) {
if (rstStatusCode != -1) {
rstStatusCode = statusCode;
inFinished = true;
outFinished = true;
notifyAll();
}
}
/**
* An input stream that reads the incoming data frames of a stream. Although
* this class uses synchronization to safely receive incoming data frames,
* it is not intended for use by multiple readers.
*/
private final class SpdyDataInputStream extends InputStream {
/*
* Store incoming data bytes in a circular buffer. When the buffer is
* empty, pos == -1. Otherwise pos is the first byte to read and limit
* is the first byte to write.
*
* { - - - X X X X - - - }
* ^ ^
* pos limit
*
* { X X X - - - - X X X }
* ^ ^
* limit pos
*/
private final byte[] buffer = new byte[64 * 1024]; // 64KiB specified by TODO
/** the next byte to be read, or -1 if the buffer is empty. Never buffer.length */
private int pos = -1;
/** the last byte to be read. Never buffer.length */
private int limit;
/** True if the caller has closed this stream. */
private boolean closed;
@Override public int available() throws IOException {
synchronized (SpdyStream.this) {
checkNotClosed();
if (pos == -1) {
return 0;
} else if (limit > pos) {
return limit - pos;
} else {
return limit + (buffer.length - pos);
}
}
}
@Override public int read() throws IOException {
return Streams.readSingleByte(this);
}
@Override public int read(byte[] b, int offset, int count) throws IOException {
synchronized (SpdyStream.this) {
checkNotClosed();
Libcore.checkOffsetAndCount(b.length, offset, count);
while (pos == -1 && !inFinished) {
try {
SpdyStream.this.wait();
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
if (pos == -1) {
return -1;
}
int copied = 0;
// drain from [pos..buffer.length)
if (limit <= pos) {
int bytesToCopy = Math.min(count, buffer.length - pos);
System.arraycopy(buffer, pos, b, offset, bytesToCopy);
pos += bytesToCopy;
copied += bytesToCopy;
if (pos == buffer.length) {
pos = 0;
}
}
// drain from [pos..limit)
if (copied < count) {
int bytesToCopy = Math.min(limit - pos, count - copied);
System.arraycopy(buffer, pos, b, offset + copied, bytesToCopy);
pos += bytesToCopy;
copied += bytesToCopy;
}
// TODO: notify peer of flow-control
if (pos == limit) {
pos = -1;
limit = 0;
}
return copied;
}
}
void receive(InputStream in, int byteCount) throws IOException {
if (inFinished) {
return; // ignore this; probably a benign race
}
if (byteCount == 0) {
return;
}
if (byteCount > buffer.length - available()) {
throw new IOException(); // TODO: RST the stream
}
// fill [limit..buffer.length)
if (pos < limit) {
int firstCopyCount = Math.min(byteCount, buffer.length - limit);
Streams.readFully(in, buffer, limit, firstCopyCount);
limit += firstCopyCount;
byteCount -= firstCopyCount;
if (limit == buffer.length) {
limit = 0;
}
}
// fill [limit..pos)
if (byteCount > 0) {
Streams.readFully(in, buffer, limit, byteCount);
limit += byteCount;
}
if (pos == -1) {
pos = 0;
SpdyStream.this.notifyAll();
}
}
@Override public void close() throws IOException {
closed = true;
// TODO: send RST to peer if !inFinished
}
private void checkNotClosed() throws IOException {
if (closed) {
throw new IOException("stream closed");
}
}
}
/**
* An output stream that writes outgoing data frames of a stream. This class
* is not thread safe.
*/
private final class SpdyDataOutputStream extends OutputStream {
private final byte[] buffer = new byte[8192];
private int pos = DATA_FRAME_HEADER_LENGTH;
/** True if the caller has closed this stream. */
private boolean closed;
@Override public void write(int b) throws IOException {
Streams.writeSingleByte(this, b);
}
@Override public void write(byte[] bytes, int offset, int count) throws IOException {
Libcore.checkOffsetAndCount(bytes.length, offset, count);
checkNotClosed();
while (count > 0) {
if (pos == buffer.length) {
writeFrame(false);
}
int bytesToCopy = Math.min(count, buffer.length - pos);
System.arraycopy(bytes, offset, buffer, pos, bytesToCopy);
pos += bytesToCopy;
offset += bytesToCopy;
count -= bytesToCopy;
}
}
@Override public void flush() throws IOException {
checkNotClosed();
if (pos > DATA_FRAME_HEADER_LENGTH) {
writeFrame(false);
connection.flush();
}
}
@Override public void close() throws IOException {
if (!closed) {
closed = true;
writeFrame(true);
connection.flush();
}
}
private void writeFrame(boolean last) throws IOException {
int flags = 0;
if (last) {
flags |= SpdyConnection.FLAG_FIN;
}
int length = pos - DATA_FRAME_HEADER_LENGTH;
Libcore.pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN);
Libcore.pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN);
connection.writeFrame(buffer, 0, pos);
pos = DATA_FRAME_HEADER_LENGTH;
}
private void checkNotClosed() throws IOException {
synchronized (SpdyStream.this) {
if (closed) {
throw new IOException("stream closed");
}
if (outFinished) {
throw new IOException("output stream finished "
+ "(RST status code=" + rstStatusCode + ")");
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy