/**
   * Encodes given object to a byte array and returns flags that describe the type of serialized
   * object.
   *
   * @param obj Object to serialize.
   * @param out Output stream to which object should be written.
   * @return Serialization flags.
   * @throws GridException If JDK serialization failed.
   */
  private int encodeObj(Object obj, ByteArrayOutputStream out) throws GridException {
    int flags = 0;

    byte[] data = null;

    if (obj instanceof String) {
      data = ((String) obj).getBytes();
    } else if (obj instanceof Boolean) {
      data = new byte[] {(byte) ((Boolean) obj ? '1' : '0')};

      flags |= BOOLEAN_FLAG;
    } else if (obj instanceof Integer) {
      data = U.intToBytes((Integer) obj);

      flags |= INT_FLAG;
    } else if (obj instanceof Long) {
      data = U.longToBytes((Long) obj);

      flags |= LONG_FLAG;
    } else if (obj instanceof Date) {
      data = U.longToBytes(((Date) obj).getTime());

      flags |= DATE_FLAG;
    } else if (obj instanceof Byte) {
      data = new byte[] {(Byte) obj};

      flags |= BYTE_FLAG;
    } else if (obj instanceof Float) {
      data = U.intToBytes(Float.floatToIntBits((Float) obj));

      flags |= FLOAT_FLAG;
    } else if (obj instanceof Double) {
      data = U.longToBytes(Double.doubleToLongBits((Double) obj));

      flags |= DOUBLE_FLAG;
    } else if (obj instanceof byte[]) {
      data = (byte[]) obj;

      flags |= BYTE_ARR_FLAG;
    } else {
      jdkMarshaller.marshal(obj, out);

      flags |= SERIALIZED_FLAG;
    }

    if (data != null) out.write(data, 0, data.length);

    return flags;
  }
  /**
   * Parses custom packet serialized by hessian marshaller.
   *
   * @param ses Session.
   * @param buf Buffer containing not parsed bytes.
   * @param state Parser state.
   * @return Parsed message.
   * @throws IOException If packet parsing or deserialization failed.
   */
  @Nullable
  private GridClientMessage parseCustomPacket(GridNioSession ses, ByteBuffer buf, ParserState state)
      throws IOException {
    assert state.packetType() == PacketType.GRIDGAIN;
    assert state.packet() == null;

    ByteArrayOutputStream tmp = state.buffer();

    int len = state.index();

    while (buf.remaining() > 0) {
      byte b = buf.get();

      if (len == 0) {
        tmp.write(b);

        if (tmp.size() == 4) {
          len = U.bytesToInt(tmp.toByteArray(), 0);

          tmp.reset();

          if (len == 0) return PING_MESSAGE;
          else if (len < 0)
            throw new IOException(
                "Failed to parse incoming packet (invalid packet length) [ses="
                    + ses
                    + ", len="
                    + len
                    + ']');

          state.index(len);
        }
      } else {
        tmp.write(b);

        if (tmp.size() == len) return marshaller.unmarshal(tmp.toByteArray());
      }
    }

    return null;
  }
  /**
   * Encodes memcache message to a raw byte array.
   *
   * @param msg Message being serialized.
   * @return Serialized message.
   * @throws GridException If serialization failed.
   */
  private ByteBuffer encodeMemcache(GridTcpRestPacket msg) throws GridException {
    GridByteArrayList res = new GridByteArrayList(HDR_LEN);

    int keyLength = 0;

    int keyFlags = 0;

    if (msg.key() != null) {
      ByteArrayOutputStream rawKey = new ByteArrayOutputStream();

      keyFlags = encodeObj(msg.key(), rawKey);

      msg.key(rawKey.toByteArray());

      keyLength = rawKey.size();
    }

    int dataLength = 0;

    int valFlags = 0;

    if (msg.value() != null) {
      ByteArrayOutputStream rawVal = new ByteArrayOutputStream();

      valFlags = encodeObj(msg.value(), rawVal);

      msg.value(rawVal.toByteArray());

      dataLength = rawVal.size();
    }

    int flagsLength = 0;

    if (msg.addFlags()) // || keyFlags > 0 || valFlags > 0)
    flagsLength = FLAGS_LENGTH;

    res.add(MEMCACHE_RES_FLAG);

    res.add(msg.operationCode());

    // Cast is required due to packet layout.
    res.add((short) keyLength);

    // Cast is required due to packet layout.
    res.add((byte) flagsLength);

    // Data type is always 0x00.
    res.add((byte) 0x00);

    res.add((short) msg.status());

    res.add(keyLength + flagsLength + dataLength);

    res.add(msg.opaque(), 0, msg.opaque().length);

    // CAS, unused.
    res.add(0L);

    assert res.size() == HDR_LEN;

    if (flagsLength > 0) {
      res.add((short) keyFlags);
      res.add((short) valFlags);
    }

    assert msg.key() == null || msg.key() instanceof byte[];
    assert msg.value() == null || msg.value() instanceof byte[];

    if (keyLength > 0) res.add((byte[]) msg.key(), 0, ((byte[]) msg.key()).length);

    if (dataLength > 0) res.add((byte[]) msg.value(), 0, ((byte[]) msg.value()).length);

    return ByteBuffer.wrap(res.entireArray());
  }
  /**
   * Parses memcache protocol message.
   *
   * @param ses Session.
   * @param buf Buffer containing not parsed bytes.
   * @param state Current parser state.
   * @return Parsed packet.s
   * @throws IOException If packet cannot be parsed.
   * @throws GridException If deserialization error occurred.
   */
  @Nullable
  private GridClientMessage parseMemcachePacket(
      GridNioSession ses, ByteBuffer buf, ParserState state) throws IOException, GridException {
    assert state.packetType() == PacketType.MEMCACHE;
    assert state.packet() != null;
    assert state.packet() instanceof GridTcpRestPacket;

    GridTcpRestPacket req = (GridTcpRestPacket) state.packet();
    ByteArrayOutputStream tmp = state.buffer();
    int i = state.index();

    while (buf.remaining() > 0) {
      byte b = buf.get();

      if (i == 0) req.requestFlag(b);
      else if (i == 1) req.operationCode(b);
      else if (i == 2 || i == 3) {
        tmp.write(b);

        if (i == 3) {
          req.keyLength(U.bytesToShort(tmp.toByteArray(), 0));

          tmp.reset();
        }
      } else if (i == 4) req.extrasLength(b);
      else if (i >= 8 && i <= 11) {
        tmp.write(b);

        if (i == 11) {
          req.totalLength(U.bytesToInt(tmp.toByteArray(), 0));

          tmp.reset();
        }
      } else if (i >= 12 && i <= 15) {
        tmp.write(b);

        if (i == 15) {
          req.opaque(tmp.toByteArray());

          tmp.reset();
        }
      } else if (i >= HDR_LEN && i < HDR_LEN + req.extrasLength()) {
        tmp.write(b);

        if (i == HDR_LEN + req.extrasLength() - 1) {
          req.extras(tmp.toByteArray());

          tmp.reset();
        }
      } else if (i >= HDR_LEN + req.extrasLength()
          && i < HDR_LEN + req.extrasLength() + req.keyLength()) {
        tmp.write(b);

        if (i == HDR_LEN + req.extrasLength() + req.keyLength() - 1) {
          req.key(tmp.toByteArray());

          tmp.reset();
        }
      } else if (i >= HDR_LEN + req.extrasLength() + req.keyLength()
          && i < HDR_LEN + req.totalLength()) {
        tmp.write(b);

        if (i == HDR_LEN + req.totalLength() - 1) {
          req.value(tmp.toByteArray());

          tmp.reset();
        }
      }

      if (i == HDR_LEN + req.totalLength() - 1)
        // Assembled the packet.
        return assemble(ses, req);

      i++;
    }

    state.index(i);

    return null;
  }