/**
   * Examines the channel buffer and attempts to match the protocol of the request and invoke the
   * matching {@link ProtocolInitiator}.
   *
   * @param ctx The channel handler context
   * @param channel The channel
   * @param bufferx The message buffer
   * @param e The channel event
   * @return The channel buffer to send upstream, or null if we need more bytes
   */
  protected ChannelBuffer protocolSwitch(
      ChannelHandlerContext ctx, Channel channel, ChannelBuffer bufferx, ChannelEvent e) {
    ChannelBuffer cb = preSwitchedBuffer.get(channel);
    if (cb != null) {
      cb.writeBytes(bufferx);
      cb.resetReaderIndex();
    } else {
      cb = bufferx;
    }
    // this guy will be set with a matching initiator
    ProtocolInitiator selectedInitiator = null;
    // this guy will be set to false if at least 1 initiator had insufficient bytes to match
    boolean sufficientBytes = true;
    // ths guy has the total bytes available in the buffer
    final int bytesAvailable = cb.readableBytes();

    for (ProtocolInitiator pi : initiators.values()) {
      if (pi.requiredBytes() > bytesAvailable) {
        sufficientBytes = false;
      } else {
        if (pi.match(cb)) {
          selectedInitiator = pi;
          break;
        }
      }
    }

    if (selectedInitiator == null) {
      // we did not get a match
      if (!sufficientBytes) {
        // ok, we did not have enough bytes
        DynamicChannelBuffer dcb = preSwitchedBuffer.get(channel);
        if (dcb == null) {
          dcb = new DynamicChannelBuffer(cb.order(), 1024, chanelBufferFactory);
          preSwitchedBuffer.set(channel, dcb);
        }
        dcb.writeBytes(cb);
        dcb.resetReaderIndex();
        return null;
      }
      // darn, we have enough bytes for any of the inits,
      // but none matched
      throw new RuntimeException("Failed to match any protocol initiator");
    }
    preSwitchedBuffer.remove(channel);
    // we matched on an initiator, so have it modify the pipeline
    selectedInitiator.modifyPipeline(ctx, channel, cb);
    return cb;

    // if we get here, it means we did not find a protocol match
    // so pass to the default protocol initiator.
  }
  @Override
  protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
    if (!(msg instanceof ChannelBuffer)) {
      return msg;
    }

    ChannelBuffer body = (ChannelBuffer) msg;
    ChannelBuffer header =
        channel.getConfig().getBufferFactory().getBuffer(body.order(), lengthFieldLength);

    int length =
        lengthIncludesLengthFieldLength
            ? body.readableBytes() + lengthFieldLength
            : body.readableBytes();
    switch (lengthFieldLength) {
      case 1:
        if (length >= 256) {
          throw new IllegalArgumentException("length does not fit into a byte: " + length);
        }
        header.writeByte((byte) length);
        break;
      case 2:
        if (length >= 65536) {
          throw new IllegalArgumentException("length does not fit into a short integer: " + length);
        }
        header.writeShort((short) length);
        break;
      case 3:
        if (length >= 16777216) {
          throw new IllegalArgumentException(
              "length does not fit into a medium integer: " + length);
        }
        header.writeMedium(length);
        break;
      case 4:
        header.writeInt(length);
        break;
      case 8:
        header.writeLong(length);
        break;
      default:
        throw new Error("should not reach here");
    }
    return wrappedBuffer(header, body);
  }