@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();
  }