/**
   * Process a SPDY connection. Called in the input thread, should not block.
   *
   * @throws IOException
   */
  protected int handleFrame() throws IOException {
    if (inFrame.c) {
      switch (inFrame.type) {
        case TYPE_SETTINGS:
          {
            int cnt = inFrame.readInt();
            for (int i = 0; i < cnt; i++) {
              inFrame.readByte();
              inFrame.read24();
              inFrame.readInt();
            }
            // TODO: save/interpret settings
            break;
          }
        case TYPE_GOAWAY:
          {
            int lastStream = inFrame.readInt();
            log.info("GOAWAY last=" + lastStream);

            // Server will shut down - but will keep processing the current requests,
            // up to lastStream. If we sent any new ones - they need to be canceled.
            abort("GO_AWAY", lastStream);
            goAway = lastStream;
            return CLOSE;
          }
        case TYPE_RST_STREAM:
          {
            inFrame.streamId = inFrame.read32();
            int errCode = inFrame.read32();
            if (SpdyContext.debug) {
              trace(
                  "> RST "
                      + inFrame.streamId
                      + " "
                      + ((errCode < RST_ERRORS.length)
                          ? RST_ERRORS[errCode]
                          : Integer.valueOf(errCode)));
            }
            SpdyStream sch;
            synchronized (channels) {
              sch = channels.remove(Integer.valueOf(inFrame.streamId));
            }
            // if RST stream is for a closed channel - we can ignore.
            if (sch != null) {
              sch.onReset();
            }

            inFrame = null;
            break;
          }
        case TYPE_SYN_STREAM:
          {
            SpdyStream ch = getSpdyContext().getStream(this);

            synchronized (channels) {
              channels.put(Integer.valueOf(inFrame.streamId), ch);
            }

            try {
              ch.onCtlFrame(inFrame);
              inFrame = null;
            } catch (Throwable t) {
              log.log(Level.SEVERE, "Error parsing head SYN_STREAM", t);
              abort("Error reading headers " + t);
              return CLOSE;
            }
            spdyContext.onStream(this, ch);
            break;
          }
        case TYPE_SYN_REPLY:
          {
            SpdyStream sch;
            synchronized (channels) {
              sch = channels.get(Integer.valueOf(inFrame.streamId));
            }
            if (sch == null) {
              abort("Missing channel");
              return CLOSE;
            }
            try {
              sch.onCtlFrame(inFrame);
              inFrame = null;
            } catch (Throwable t) {
              log.info("Error parsing head SYN_STREAM" + t);
              abort("Error reading headers " + t);
              return CLOSE;
            }
            break;
          }
        case TYPE_PING:
          {
            SpdyFrame oframe = getSpdyContext().getFrame();
            oframe.type = TYPE_PING;
            oframe.c = true;

            oframe.append32(inFrame.read32());
            oframe.pri = 0x80;

            send(oframe, null);
            break;
          }
      }
    } else {
      // Data frame
      SpdyStream sch;
      synchronized (channels) {
        sch = channels.get(Integer.valueOf(inFrame.streamId));
      }
      if (sch == null) {
        abort("Missing channel");
        return CLOSE;
      }
      sch.onDataFrame(inFrame);
      synchronized (channels) {
        if (sch.finRcvd && sch.finSent) {
          channels.remove(Integer.valueOf(inFrame.streamId));
        }
      }
      inFrame = null;
    }
    return LONG;
  }