@Override
  public void syn(SynInfo synInfo, StreamFrameListener listener, Promise<Stream> promise) {
    // Synchronization is necessary.
    // SPEC v3, 2.3.1 requires that the stream creation be monotonically crescent
    // so we cannot allow thread1 to create stream1 and thread2 create stream3 and
    // have stream3 hit the network before stream1, not only to comply with the spec
    // but also because the compression context for the headers would be wrong, as the
    // frame with a compression history will come before the first compressed frame.
    int associatedStreamId = 0;
    if (synInfo instanceof PushSynInfo)
      associatedStreamId = ((PushSynInfo) synInfo).getAssociatedStreamId();

    synchronized (this) {
      int streamId = streamIds.getAndAdd(2);
      // TODO: for SPDYv3 we need to support the "slot" argument
      SynStreamFrame synStream =
          new SynStreamFrame(
              version,
              synInfo.getFlags(),
              streamId,
              associatedStreamId,
              synInfo.getPriority(),
              (short) 0,
              synInfo.getHeaders());
      IStream stream = createStream(synStream, listener, true, promise);
      if (stream == null) return;
      generateAndEnqueueControlFrame(
          stream, synStream, synInfo.getTimeout(), synInfo.getUnit(), stream);
    }
    flush();
  }
 @Override
 public void data(
     IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Callback callback) {
   LOG.debug("Queuing {} on {}", dataInfo, stream);
   DataFrameBytes frameBytes = new DataFrameBytes(stream, callback, dataInfo);
   if (timeout > 0) frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
   append(frameBytes);
   flush();
 }
 private void onPing(PingFrame frame) {
   int pingId = frame.getPingId();
   if (pingId % 2 == pingIds.get() % 2) {
     PingResultInfo pingResultInfo = new PingResultInfo(frame.getPingId());
     notifyOnPing(listener, pingResultInfo);
     flush();
   } else {
     control(null, frame, 0, TimeUnit.MILLISECONDS, new Callback.Adapter());
   }
 }
 private void onGoAway(GoAwayFrame frame) {
   if (goAwayReceived.compareAndSet(false, true)) {
     // TODO: Find a better name for GoAwayResultInfo
     GoAwayResultInfo goAwayResultInfo =
         new GoAwayResultInfo(frame.getLastStreamId(), SessionStatus.from(frame.getStatusCode()));
     notifyOnGoAway(listener, goAwayResultInfo);
     flush();
     // SPDY does not require to send back a response to a GO_AWAY.
     // We notified the application of the last good stream id and
     // tried our best to flush remaining data.
   }
 }
  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);
  }
 private void onSettings(SettingsFrame frame) {
   Settings.Setting windowSizeSetting = frame.getSettings().get(Settings.ID.INITIAL_WINDOW_SIZE);
   if (windowSizeSetting != null) {
     int windowSize = windowSizeSetting.value();
     setWindowSize(windowSize);
     LOG.debug("Updated session window size to {}", windowSize);
   }
   Settings.Setting maxConcurrentStreamsSetting =
       frame.getSettings().get(Settings.ID.MAX_CONCURRENT_STREAMS);
   if (maxConcurrentStreamsSetting != null) {
     int maxConcurrentStreamsValue = maxConcurrentStreamsSetting.value();
     maxConcurrentLocalStreams = maxConcurrentStreamsValue;
     LOG.debug("Updated session maxConcurrentLocalStreams to {}", maxConcurrentStreamsValue);
   }
   SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(), frame.isClearPersisted());
   notifyOnSettings(listener, settingsInfo);
   flush();
 }
 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 shutdown() {
   FrameBytes frameBytes = new CloseFrameBytes();
   append(frameBytes);
   flush();
 }
 @Override
 public void control(
     IStream stream, ControlFrame frame, long timeout, TimeUnit unit, Callback callback) {
   generateAndEnqueueControlFrame(stream, frame, timeout, unit, callback);
   flush();
 }
 private void onCredential(CredentialFrame frame) {
   LOG.warn("{} frame not yet supported", frame.getType());
   flush();
 }
 private void onWindowUpdate(WindowUpdateFrame frame) {
   int streamId = frame.getStreamId();
   IStream stream = streams.get(streamId);
   flowControlStrategy.onWindowUpdate(this, stream, frame.getWindowDelta());
   flush();
 }