@Override public void newStream(HeadersFrame frame, Promise<Stream> promise, Stream.Listener listener) { // Synchronization is necessary to atomically create // the stream id and enqueue the frame to be sent. boolean queued; synchronized (this) { int streamId = frame.getStreamId(); if (streamId <= 0) { streamId = streamIds.getAndAdd(2); PriorityFrame priority = frame.getPriority(); priority = priority == null ? null : new PriorityFrame( streamId, priority.getParentStreamId(), priority.getWeight(), priority.isExclusive()); frame = new HeadersFrame(streamId, frame.getMetaData(), priority, frame.isEndStream()); } final IStream stream = createLocalStream(streamId, promise); if (stream == null) return; stream.setListener(listener); ControlEntry entry = new ControlEntry(frame, stream, new PromiseCallback<>(promise, stream)); queued = flusher.append(entry); } // Iterate outside the synchronized block. if (queued) flusher.iterate(); }
protected IStream createRemoteStream(int streamId) { // SPEC: exceeding max concurrent streams is treated as stream error. while (true) { int remoteCount = remoteStreamCount.get(); int maxCount = getMaxRemoteStreams(); if (maxCount >= 0 && remoteCount >= maxCount) { reset(new ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP); return null; } if (remoteStreamCount.compareAndSet(remoteCount, remoteCount + 1)) break; } IStream stream = newStream(streamId, false); // SPEC: duplicate stream is treated as connection error. if (streams.putIfAbsent(streamId, stream) == null) { updateLastStreamId(streamId); stream.setIdleTimeout(getStreamIdleTimeout()); flowControl.onStreamCreated(stream); if (LOG.isDebugEnabled()) LOG.debug("Created remote {}", stream); return stream; } else { close(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream", Callback.NOOP); return null; } }
@Override public void onReset(ResetFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); IStream stream = getStream(frame.getStreamId()); if (stream != null) stream.process(frame, Callback.NOOP); else notifyReset(this, frame); }
private void processData(final IStream stream, DataFrame frame, ByteBuffer data) { ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose()) { @Override public void consume(int delta) { super.consume(delta); flowControlStrategy.onDataConsumed(StandardSession.this, stream, this, delta); } }; flowControlStrategy.onDataReceived(this, stream, dataInfo); stream.process(dataInfo); if (stream.isClosed()) removeStream(stream); }
private void removeStream(IStream stream) { if (stream.isUnidirectional()) stream.getAssociatedStream().disassociate(stream); IStream removed = streams.remove(stream.getId()); if (removed != null) { assert removed == stream; if (streamIds.get() % 2 == stream.getId() % 2) localStreamCount.decrementAndGet(); LOG.debug("Removed {}", stream); notifyStreamClosed(stream); } }
private void onRst(RstStreamFrame frame) { IStream stream = streams.get(frame.getStreamId()); if (stream != null) stream.process(frame); RstInfo rstInfo = new RstInfo( frame.getStreamId(), StreamStatus.from(frame.getVersion(), frame.getStatusCode())); notifyOnRst(listener, rstInfo); flush(); if (stream != null) removeStream(stream); }
@Override public int compareTo(FrameBytes that) { // FrameBytes may have or not have a related stream (for example, PING do not have a related // stream) // FrameBytes without related streams have higher priority IStream thisStream = getStream(); IStream thatStream = that.getStream(); if (thisStream == null) return thatStream == null ? 0 : -1; if (thatStream == null) return 1; // If this.stream.priority > that.stream.priority => this.stream has less priority than // that.stream return thatStream.getPriority() - thisStream.getPriority(); }
@Override public void complete() { bufferPool.release(buffer); super.complete(); if (frame.getType() == ControlFrameType.GO_AWAY) { // After sending a GO_AWAY we need to hard close the connection. // Recipients will know the last good stream id and act accordingly. close(); } IStream stream = getStream(); if (stream != null && stream.isClosed()) removeStream(stream); }
@Override public void onWindowUpdate(WindowUpdateFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); int streamId = frame.getStreamId(); if (streamId > 0) { IStream stream = getStream(streamId); if (stream != null) { stream.process(frame, Callback.NOOP); onWindowUpdate(stream, frame); } } else { onWindowUpdate(null, frame); } }
@Override public void removeStream(IStream stream) { IStream removed = streams.remove(stream.getId()); if (removed != null) { assert removed == stream; boolean local = stream.isLocal(); if (local) localStreamCount.decrementAndGet(); else remoteStreamCount.decrementAndGet(); onStreamClosed(stream); flowControl.onStreamDestroyed(stream); if (LOG.isDebugEnabled()) LOG.debug("Removed {} {}", local ? "local" : "remote", stream); } }
@Override public void flush() { FrameBytes frameBytes = null; ByteBuffer buffer = null; boolean failFrameBytes = false; synchronized (queue) { if (flushing || queue.isEmpty()) return; Set<IStream> stalledStreams = null; for (int i = 0; i < queue.size(); ++i) { frameBytes = queue.get(i); IStream stream = frameBytes.getStream(); if (stream != null && stalledStreams != null && stalledStreams.contains(stream)) continue; buffer = frameBytes.getByteBuffer(); if (buffer != null) { queue.remove(i); if (stream != null && stream.isReset() && !(frameBytes instanceof ControlFrameBytes)) failFrameBytes = true; break; } if (stalledStreams == null) stalledStreams = new HashSet<>(); if (stream != null) stalledStreams.add(stream); LOG.debug("Flush stalled for {}, {} frame(s) in queue", frameBytes, queue.size()); } if (buffer == null) return; if (!failFrameBytes) { flushing = true; LOG.debug("Flushing {}, {} frame(s) in queue", frameBytes, queue.size()); } } if (failFrameBytes) { frameBytes.fail( new StreamException( frameBytes.getStream().getId(), StreamStatus.INVALID_STREAM, "Stream: " + frameBytes.getStream() + " is reset!")); } else { write(buffer, frameBytes); } }
@Override public void rst(RstInfo rstInfo, Callback callback) { // SPEC v3, 2.2.2 if (goAwaySent.get()) { complete(callback); } else { int streamId = rstInfo.getStreamId(); IStream stream = streams.get(streamId); RstStreamFrame frame = new RstStreamFrame(version, streamId, rstInfo.getStreamStatus().getCode(version)); control(stream, frame, rstInfo.getTimeout(), rstInfo.getUnit(), callback); if (stream != null) { stream.process(frame); removeStream(stream); } } }
@Override public ByteBuffer getByteBuffer() { try { IStream stream = getStream(); int windowSize = stream.getWindowSize(); if (windowSize <= 0) return null; size = dataInfo.available(); if (size > windowSize) size = windowSize; buffer = generator.data(stream.getId(), size, dataInfo); return buffer; } catch (Throwable x) { fail(x); return null; } }
@Override public void onData(final DataFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Received {}", frame); int streamId = frame.getStreamId(); final IStream stream = getStream(streamId); // SPEC: the session window must be updated even if the stream is null. // The flow control length includes the padding bytes. final int flowControlLength = frame.remaining() + frame.padding(); flowControl.onDataReceived(this, stream, flowControlLength); if (stream != null) { if (getRecvWindow() < 0) { close(ErrorCode.FLOW_CONTROL_ERROR.code, "session_window_exceeded", Callback.NOOP); } else { stream.process( frame, new Callback() { @Override public void succeeded() { complete(); } @Override public void failed(Throwable x) { // Consume also in case of failures, to free the // session flow control window for other streams. complete(); } private void complete() { notIdle(); stream.notIdle(); flowControl.onDataConsumed(HTTP2Session.this, stream, flowControlLength); } }); } } else { if (LOG.isDebugEnabled()) LOG.debug("Ignoring {}, stream #{} not found", frame, streamId); // We must enlarge the session flow control window, // otherwise other requests will be stalled. flowControl.onDataConsumed(this, null, flowControlLength); } }
private void processSyn(SessionFrameListener listener, IStream stream, SynStreamFrame frame) { stream.process(frame); // Update the last stream id before calling the application (which may send a GO_AWAY) updateLastStreamId(stream); StreamFrameListener streamListener; if (stream.isUnidirectional()) { PushInfo pushInfo = new PushInfo(frame.getHeaders(), frame.isClose()); streamListener = notifyOnPush(stream.getAssociatedStream().getStreamFrameListener(), stream, pushInfo); } else { SynInfo synInfo = new SynInfo(frame.getHeaders(), frame.isClose(), frame.getPriority()); streamListener = notifyOnSyn(listener, stream, synInfo); } stream.setStreamFrameListener(streamListener); flush(); // The onSyn() listener may have sent a frame that closed the stream if (stream.isClosed()) removeStream(stream); }
@Override public void complete() { bufferPool.release(buffer); IStream stream = getStream(); dataInfo.consume(size); flowControlStrategy.updateWindow(StandardSession.this, stream, -size); if (dataInfo.available() > 0) { // We have written a frame out of this DataInfo, but there is more to write. // We need to keep the correct ordering of frames, to avoid that another // DataInfo for the same stream is written before this one is finished. prepend(this); flush(); } else { super.complete(); stream.updateCloseState(dataInfo.isClose(), true); if (stream.isClosed()) removeStream(stream); } }
protected void notifyHeaders(IStream stream, HeadersFrame frame) { Stream.Listener listener = stream.getListener(); if (listener == null) return; try { listener.onHeaders(stream, frame); } catch (Throwable x) { LOG.info("Failure while notifying listener " + listener, x); } }
private IStream createStream( SynStreamFrame frame, StreamFrameListener listener, boolean local, Promise<Stream> promise) { IStream associatedStream = streams.get(frame.getAssociatedStreamId()); IStream stream = new StandardStream( frame.getStreamId(), frame.getPriority(), this, associatedStream, promise); flowControlStrategy.onNewStream(this, stream); stream.updateCloseState(frame.isClose(), local); stream.setStreamFrameListener(listener); if (stream.isUnidirectional()) { // Unidirectional streams are implicitly half closed stream.updateCloseState(true, !local); if (!stream.isClosed()) stream.getAssociatedStream().associate(stream); } int streamId = stream.getId(); if (local) { while (true) { int oldStreamCountValue = localStreamCount.get(); int maxConcurrentStreams = maxConcurrentLocalStreams; if (maxConcurrentStreams > -1 && oldStreamCountValue >= maxConcurrentStreams) { String message = String.format("Max concurrent local streams (%d) exceeded.", maxConcurrentStreams); LOG.debug(message); promise.failed(new SPDYException(message)); return null; } if (localStreamCount.compareAndSet(oldStreamCountValue, oldStreamCountValue + 1)) break; } } if (streams.putIfAbsent(streamId, stream) != null) { String message = "Duplicate stream id " + streamId; IllegalStateException duplicateIdException = new IllegalStateException(message); promise.failed(duplicateIdException); if (local) { localStreamCount.decrementAndGet(); throw duplicateIdException; } RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR); LOG.debug("Duplicate stream, {}", rstInfo); rst(rstInfo, new Callback.Adapter()); // We don't care (too much) if the reset fails. return null; } else { LOG.debug("Created {}", stream); notifyStreamCreated(stream); return stream; } }
@Override public void push( IStream stream, Promise<Stream> promise, PushPromiseFrame frame, Stream.Listener listener) { // Synchronization is necessary to atomically create // the stream id and enqueue the frame to be sent. boolean queued; synchronized (this) { int streamId = streamIds.getAndAdd(2); frame = new PushPromiseFrame(frame.getStreamId(), streamId, frame.getMetaData()); final IStream pushStream = createLocalStream(streamId, promise); if (pushStream == null) return; pushStream.setListener(listener); ControlEntry entry = new ControlEntry(frame, pushStream, new PromiseCallback<>(promise, pushStream)); queued = flusher.append(entry); } // Iterate outside the synchronized block. if (queued) flusher.iterate(); }
protected IStream createLocalStream(int streamId, Promise<Stream> promise) { while (true) { int localCount = localStreamCount.get(); int maxCount = getMaxLocalStreams(); if (maxCount >= 0 && localCount >= maxCount) { promise.failed( new IllegalStateException("Max local stream count " + maxCount + " exceeded")); return null; } if (localStreamCount.compareAndSet(localCount, localCount + 1)) break; } IStream stream = newStream(streamId, true); if (streams.putIfAbsent(streamId, stream) == null) { stream.setIdleTimeout(getStreamIdleTimeout()); flowControl.onStreamCreated(stream); if (LOG.isDebugEnabled()) LOG.debug("Created local {}", stream); return stream; } else { promise.failed(new IllegalStateException("Duplicate stream " + streamId)); return null; } }
private void terminate(Throwable cause) { while (true) { CloseState current = closed.get(); switch (current) { case NOT_CLOSED: case LOCALLY_CLOSED: case REMOTELY_CLOSED: { if (closed.compareAndSet(current, CloseState.CLOSED)) { flusher.terminate(cause); for (IStream stream : streams.values()) stream.close(); streams.clear(); disconnect(); return; } break; } default: { return; } } } }
private void updateLastStreamId(IStream stream) { int streamId = stream.getId(); if (streamId % 2 != streamIds.get() % 2) Atomics.updateMax(lastStreamId, streamId); }
private void processHeaders(IStream stream, HeadersFrame frame) { stream.process(frame); if (stream.isClosed()) removeStream(stream); }
private void processReply(IStream stream, SynReplyFrame frame) { stream.process(frame); if (stream.isClosed()) removeStream(stream); }
@Test @Ignore public void testSeekKeyFrameCheckIndex() { Global.setFFmpegLoggingLevel(52); IContainer container = IContainer.make(); int retval = -1; // final String // INPUT_FILE="fixtures/"+this.getClass().getName()+"-testSeekKeyFrameCheckIndex.ogg"; // final int NUM_STREAMS=2; // final ICodec.ID VID_CODEC=ICodec.ID.CODEC_ID_THEORA; // final String INPUT_FILE="fixtures/testfile_h264_mp4a_tmcd.mov"; // final int NUM_STREAMS=3; // final ICodec.ID VID_CODEC=ICodec.ID.CODEC_ID_H264; final String INPUT_FILE = "fixtures/testfile.flv"; final int NUM_STREAMS = 2; final ICodec.ID VID_CODEC = ICodec.ID.CODEC_ID_FLV1; retval = container.open(INPUT_FILE, IContainer.Type.READ, null); assertTrue("could not open file", retval >= 0); // First, let's get all the key frames final int numStreams = container.getNumStreams(); assertEquals(NUM_STREAMS, numStreams); final int vidIndex = 0; final IStream vidStream = container.getStream(vidIndex); final IStreamCoder vidCoder = vidStream.getStreamCoder(); assertEquals(VID_CODEC, vidCoder.getCodecID()); vidCoder.delete(); vidStream.delete(); // now, loop through the entire file and record the index of EACH // video key frame final IPacket packet = IPacket.make(); final ArrayList<Long> offsets = new ArrayList<Long>(1024); final ArrayList<Long> timestamps = new ArrayList<Long>(1024); long numPackets = 0; while (container.readNextPacket(packet) >= 0) { // log.debug("Next: {}", container.getByteOffset()); if (packet.isComplete()) { if (packet.getStreamIndex() != vidIndex) continue; if (!packet.isKey()) continue; // log.debug("Packet: {}", packet); if (numPackets < 5) { log.debug("First Packet: {}", packet); } ++numPackets; assertTrue(packet.getPosition() >= 0); offsets.add(packet.getPosition()); timestamps.add(packet.getDts()); } } log.debug( "Num Index Entries; container: {}; test: {}", vidStream.getNumIndexEntries(), offsets.size()); // retval = container.close(); // assertTrue("got negative retval: " + IError.errorNumberToType(retval), retval >= 0); // retval = container.open(INPUT_FILE, IContainer.Type.READ, null); // assertTrue("got negative retval: " + IError.errorNumberToType(retval), retval >= 0); // move the seek head backwards retval = container.seekKeyFrame( -1, Long.MIN_VALUE, 0, Long.MAX_VALUE, IContainer.SEEK_FLAG_BACKWARDS); assertTrue("got negative retval: " + IError.errorNumberToType(retval), retval >= 0); // now let's walk through that index and ensure we can seek to each key frame. for (int i = 0; i < offsets.size(); i++) { long index = offsets.get(i); retval = container.seekKeyFrame( vidIndex, index, index, index, // 0); IContainer.SEEK_FLAG_BYTE); assertTrue("got negative retval: " + IError.errorNumberToType(retval), retval >= 0); retval = container.readNextPacket(packet); log.debug("{}", packet); assertTrue("got negative retval: " + IError.errorNumberToType(retval), retval >= 0); assertTrue(packet.isComplete()); // assertEquals(offsets.get(i).longValue(), packet.getPosition()); // assertEquals(timestamps.get(i).longValue(), packet.getDts()); assertTrue(packet.isKey()); } container.close(); }