content: /*
   * Copyright (c) 2002, 2021, Oracle and/or its affiliates.
   *
   * This program is free software; you can redistribute it and/or modify it under
   * the terms of the GNU General Public License, version 2.0, as published by the
   * Free Software Foundation.
   *
   * This program is also distributed with certain software (including but not
   * limited to OpenSSL) that is licensed under separate terms, as designated in a
   * particular file or component or in included license documentation. The
   * authors of MySQL hereby grant you an additional permission to link the
   * program and your derivative works with the separately licensed software that
   * they have included with MySQL.
   *
   * Without limiting anything contained in the foregoing, this file, which is
   * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
   * version 1.0, a copy of which can be found at
   * http://oss.oracle.com/licenses/universal-foss-exception.
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
   * for more details.
   *
   * You should have received a copy of the GNU General Public License along with
   * this program; if not, write to the Free Software Foundation, Inc.,
   * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
   */
  
  package com.mysql.cj.protocol.a;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import com.mysql.cj.Constants;
  import com.mysql.cj.Messages;
  import com.mysql.cj.exceptions.ExceptionFactory;
  import com.mysql.cj.exceptions.WrongArgumentException;
  import com.mysql.cj.protocol.Message;
  import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType;
  import com.mysql.cj.protocol.a.NativeConstants.StringLengthDataType;
  import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType;
  import com.mysql.cj.util.StringUtils;
  
  /**
   * PacketPayload is the content of a full single packet (independent from
   * on-wire splitting) communicated with the server. We can manipulate the
   * packet's underlying buffer when sending commands with writeInteger(),
   * writeBytes(), etc. We can check the packet type with isEOFPacket(), etc
   * predicates.
   * 
   * A position is maintained for reading/writing data. A payload length is
   * maintained allowing the PacketPayload to be decoupled from the size of
   * the underlying buffer.
   */
  public class NativePacketPayload implements Message {
      static final int NO_LENGTH_LIMIT = -1;
      public static final long NULL_LENGTH = -1;
  
      /* Type ids of response packets. */
      public static final short TYPE_ID_ERROR = 0xFF;
      public static final short TYPE_ID_EOF = 0xFE;
      /** It has the same signature as EOF, but may be issued by server only during handshake phase **/
      public static final short TYPE_ID_AUTH_SWITCH = 0xFE;
      public static final short TYPE_ID_LOCAL_INFILE = 0xFB;
      public static final short TYPE_ID_OK = 0;
      public static final short TYPE_ID_AUTH_MORE_DATA = 0x01;
      public static final short TYPE_ID_AUTH_NEXT_FACTOR = 0x02;
  
      private int payloadLength = 0;
  
      private byte[] byteBuffer;
  
      private int position = 0;
  
      static final int MAX_BYTES_TO_DUMP = 1024;
  
      private Map<String, Integer> tags = new HashMap<>();
  
      @Override
      public String toString() {
          int numBytes = this.position <= this.payloadLength ? this.position : this.payloadLength;
          int numBytesToDump = numBytes < MAX_BYTES_TO_DUMP ? numBytes : MAX_BYTES_TO_DUMP;
  
          this.position = 0;
          String dumped = StringUtils.dumpAsHex(readBytes(StringLengthDataType.STRING_FIXED, numBytesToDump), numBytesToDump);
  
          if (numBytesToDump < numBytes) {
              return dumped + " ....(packet exceeds max. dump length)";
          }
  
          return dumped;
      }
  
      public String toSuperString() {
          return super.toString();
      }
  
      public NativePacketPayload(byte[] buf) {
          this.byteBuffer = buf;
          this.payloadLength = buf.length;
      }
  
      public NativePacketPayload(int size) {
          this.byteBuffer = new byte[size];
          this.payloadLength = size;
      }
  
      public int getCapacity() {
          return this.byteBuffer.length;
      }
  
      /**
       * Checks that underlying buffer has enough space to store additionalData bytes starting from current position.
       * If buffer size is smaller than required then it is re-allocated with bigger size.
       * 
       * @param additionalData
       *            additional data size in bytes
       */
      public final void ensureCapacity(int additionalData) {
          if ((this.position + additionalData) > this.byteBuffer.length) {
              //
              // Resize, and pad so we can avoid allocing again in the near future
              //
              int newLength = (int) (this.byteBuffer.length * 1.25);
  
              if (newLength < (this.byteBuffer.length + additionalData)) {
                  newLength = this.byteBuffer.length + (int) (additionalData * 1.25);
              }
  
              if (newLength < this.byteBuffer.length) {
                  newLength = this.byteBuffer.length + additionalData;
              }
  
              byte[] newBytes = new byte[newLength];
  
              System.arraycopy(this.byteBuffer, 0, newBytes, 0, this.byteBuffer.length);
              this.byteBuffer = newBytes;
          }
      }
  
      @Override
      public byte[] getByteBuffer() {
          return this.byteBuffer;
      }
  
      /**
       * Sets the array of bytes to use as a buffer to read from.
       * 
       * @param byteBufferToSet
       *            the array of bytes to use as a buffer
       */
      public void setByteBuffer(byte[] byteBufferToSet) {
          this.byteBuffer = byteBufferToSet;
      }
  
      /**
       * Get the actual length of payload the buffer contains.
       * It can be smaller than underlying buffer size because it can be reused after a big packet.
       * 
       * @return payload length
       */
      public int getPayloadLength() {
          return this.payloadLength;
      }
  
      /**
       * Set the actual length of payload written to buffer.
       * It can be smaller or equal to underlying buffer size.
       * 
       * @param bufLengthToSet
       *            length
       */
      public void setPayloadLength(int bufLengthToSet) {
          if (bufLengthToSet > this.byteBuffer.length) {
              throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Buffer.0"));
          }
          this.payloadLength = bufLengthToSet;
      }
  
      /**
       * To be called after write operations to ensure that payloadLength contains
       * the real size of written data.
       */
      private void adjustPayloadLength() {
          if (this.position > this.payloadLength) {
              this.payloadLength = this.position;
          }
      }
  
      @Override
      public int getPosition() {
          return this.position;
      }
  
      /**
       * Set the current position to write to/ read from
       * 
       * @param positionToSet
       *            the position (0-based index)
       */
      public void setPosition(int positionToSet) {
          this.position = positionToSet;
      }
  
      /**
       * Is it a ERROR packet.
       * 
       * @return true if it is a ERROR packet
       */
      public boolean isErrorPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_ERROR;
      }
  
      /**
       * Is it a EOF packet.
       * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html
       * 
       * @return true if it is a EOF packet
       */
      public final boolean isEOFPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_EOF && (this.payloadLength <= 5);
      }
  
      /**
       * Is it a Protocol::AuthSwitchRequest packet.
       * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html
       * 
       * @return true if it is a Protocol::AuthSwitchRequest packet
       */
      public final boolean isAuthMethodSwitchRequestPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_AUTH_SWITCH;
      }
  
      /**
       * Is it an OK packet.
       * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
       * 
       * @return true if it is an OK packet
       */
      public final boolean isOKPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_OK;
      }
  
      /**
       * Is it an OK packet for ResultSet. Unlike usual 0x00 signature it has 0xfe signature.
       * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
       * 
       * @return true if it is an OK packet for ResultSet
       */
      public final boolean isResultSetOKPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_EOF && (this.payloadLength > 5) && (this.payloadLength < 16777215);
      }
  
      /**
       * Is it a Protocol::AuthMoreData packet.
       * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html
       * 
       * @return true if it is a Protocol::AuthMoreData packet
       */
      public final boolean isAuthMoreDataPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_AUTH_MORE_DATA;
      }
  
      /**
       * Is it a Protocol::AuthNextFactor packet.
       * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_next_factor_request.html
       * 
       * @return true if it is a Protocol::AuthNextFactor packet
       */
      public final boolean isAuthNextFactorPacket() {
          return (this.byteBuffer[0] & 0xff) == TYPE_ID_AUTH_NEXT_FACTOR;
      }
  
      /**
       * Write data according to provided Integer type.
       * 
       * @param type
       *            {@link IntegerDataType}
       * @param l
       *            value
       */
      public void writeInteger(IntegerDataType type, long l) {
          byte[] b;
          switch (type) {
              case INT1:
                  ensureCapacity(1);
                  b = this.byteBuffer;
                  b[this.position++] = (byte) (l & 0xff);
                  break;
  
              case INT2:
                  ensureCapacity(2);
                  b = this.byteBuffer;
                  b[this.position++] = (byte) (l & 0xff);
                  b[this.position++] = (byte) (l >>> 8);
                  break;
  
              case INT3:
                  ensureCapacity(3);
                  b = this.byteBuffer;
                  b[this.position++] = (byte) (l & 0xff);
                  b[this.position++] = (byte) (l >>> 8);
                  b[this.position++] = (byte) (l >>> 16);
                  break;
  
              case INT4:
                  ensureCapacity(4);
                  b = this.byteBuffer;
                  b[this.position++] = (byte) (l & 0xff);
                  b[this.position++] = (byte) (l >>> 8);
                  b[this.position++] = (byte) (l >>> 16);
                  b[this.position++] = (byte) (l >>> 24);
                  break;
  
              case INT6:
                  ensureCapacity(6);
                  b = this.byteBuffer;
                  b[this.position++] = (byte) (l & 0xff);
                  b[this.position++] = (byte) (l >>> 8);
                  b[this.position++] = (byte) (l >>> 16);
                  b[this.position++] = (byte) (l >>> 24);
                  b[this.position++] = (byte) (l >>> 32);
                  b[this.position++] = (byte) (l >>> 40);
                  break;
  
              case INT8:
                  ensureCapacity(8);
                  b = this.byteBuffer;
                  b[this.position++] = (byte) (l & 0xff);
                  b[this.position++] = (byte) (l >>> 8);
                  b[this.position++] = (byte) (l >>> 16);
                  b[this.position++] = (byte) (l >>> 24);
                  b[this.position++] = (byte) (l >>> 32);
                  b[this.position++] = (byte) (l >>> 40);
                  b[this.position++] = (byte) (l >>> 48);
                  b[this.position++] = (byte) (l >>> 56);
                  break;
  
              case INT_LENENC:
                  if (l < 251) {
                      ensureCapacity(1);
                      writeInteger(IntegerDataType.INT1, l);
  
                  } else if (l < 65536L) {
                      ensureCapacity(3);
                      writeInteger(IntegerDataType.INT1, 252);
                      writeInteger(IntegerDataType.INT2, l);
  
                  } else if (l < 16777216L) {
                      ensureCapacity(4);
                      writeInteger(IntegerDataType.INT1, 253);
                      writeInteger(IntegerDataType.INT3, l);
  
                  } else {
                      ensureCapacity(9);
                      writeInteger(IntegerDataType.INT1, 254);
                      writeInteger(IntegerDataType.INT8, l);
                  }
          }
  
          adjustPayloadLength();
      }
  
      /**
       * Read data according to provided Integer type.
       * 
       * @param type
       *            {@link IntegerDataType}
       * @return long
       */
      public final long readInteger(IntegerDataType type) {
          byte[] b = this.byteBuffer;
          switch (type) {
              case INT1:
                  return (b[this.position++] & 0xff);
  
              case INT2:
                  return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8);
  
              case INT3:
                  return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8) | ((b[this.position++] & 0xff) << 16);
  
              case INT4:
                  return ((long) b[this.position++] & 0xff) | (((long) b[this.position++] & 0xff) << 8) | ((long) (b[this.position++] & 0xff) << 16)
                          | ((long) (b[this.position++] & 0xff) << 24);
  
              case INT6:
                  return (b[this.position++] & 0xff) | ((long) (b[this.position++] & 0xff) << 8) | ((long) (b[this.position++] & 0xff) << 16)
                          | ((long) (b[this.position++] & 0xff) << 24) | ((long) (b[this.position++] & 0xff) << 32) | ((long) (b[this.position++] & 0xff) << 40);
  
              case INT8:
                  return (b[this.position++] & 0xff) | ((long) (b[this.position++] & 0xff) << 8) | ((long) (b[this.position++] & 0xff) << 16)
                          | ((long) (b[this.position++] & 0xff) << 24) | ((long) (b[this.position++] & 0xff) << 32) | ((long) (b[this.position++] & 0xff) << 40)
                          | ((long) (b[this.position++] & 0xff) << 48) | ((long) (b[this.position++] & 0xff) << 56);
  
              case INT_LENENC:
                  int sw = b[this.position++] & 0xff;
                  switch (sw) {
                      case 251:
                          return NULL_LENGTH; // represents a NULL in a ProtocolText::ResultsetRow
                      case 252:
                          return readInteger(IntegerDataType.INT2);
                      case 253:
                          return readInteger(IntegerDataType.INT3);
                      case 254:
                          return readInteger(IntegerDataType.INT8);
                      default:
                          return sw;
                  }
  
              default:
                  return (b[this.position++] & 0xff);
          }
      }
  
      /**
       * Write all bytes from given byte array into internal buffer starting with current buffer position.
       * 
       * @param type
       *            on-wire data type
       * @param b
       *            from byte array
       */
      public final void writeBytes(StringSelfDataType type, byte[] b) {
          writeBytes(type, b, 0, b.length);
      }
  
      /**
       * Write all bytes from given byte array into internal buffer starting with current buffer position.
       * 
       * @param type
       *            on-wire data type
       * @param b
       *            from byte array
       */
      public final void writeBytes(StringLengthDataType type, byte[] b) {
          writeBytes(type, b, 0, b.length);
      }
  
      /**
       * Write len bytes from given byte array into internal buffer.
       * Read starts from given offset, write starts with current buffer position.
       * 
       * @param type
       *            on-wire data type
       * @param b
       *            from byte array
       * @param offset
       *            starting index of b
       * @param len
       *            number of bytes to be written
       */
      public void writeBytes(StringSelfDataType type, byte[] b, int offset, int len) {
          switch (type) {
              case STRING_EOF:
                  writeBytes(StringLengthDataType.STRING_FIXED, b, offset, len);
                  break;
  
              case STRING_TERM:
                  ensureCapacity(len + 1);
                  writeBytes(StringLengthDataType.STRING_FIXED, b, offset, len);
                  this.byteBuffer[this.position++] = 0;
                  break;
  
              case STRING_LENENC:
                  ensureCapacity(len + 9);
                  writeInteger(IntegerDataType.INT_LENENC, len);
                  writeBytes(StringLengthDataType.STRING_FIXED, b, offset, len);
                  break;
          }
  
          adjustPayloadLength();
      }
  
      /**
       * Write len bytes from given byte array into internal buffer.
       * Read starts from given offset, write starts with current buffer position.
       * 
       * @param type
       *            on-wire data type
       * @param b
       *            from byte array
       * @param offset
       *            starting index of b
       * @param len
       *            number of bytes to be written
       */
      public void writeBytes(StringLengthDataType type, byte[] b, int offset, int len) {
          switch (type) {
              case STRING_FIXED:
              case STRING_VAR:
                  ensureCapacity(len);
                  System.arraycopy(b, offset, this.byteBuffer, this.position, len);
                  this.position += len;
                  break;
          }
  
          adjustPayloadLength();
      }
  
      /**
       * Read bytes from internal buffer starting from current position into the new byte array.
       * The length of data to read depends on {@link StringSelfDataType}.
       * 
       * @param type
       *            {@link StringSelfDataType}
       * @return bytes
       */
      public byte[] readBytes(StringSelfDataType type) {
          byte[] b;
          switch (type) {
              case STRING_TERM:
                  int i = this.position;
                  while ((i < this.payloadLength) && (this.byteBuffer[i] != 0)) {
                      i++;
                  }
                  b = readBytes(StringLengthDataType.STRING_FIXED, i - this.position);
                  this.position++; // skip terminating byte
                  return b;
  
              case STRING_LENENC:
                  long l = readInteger(IntegerDataType.INT_LENENC);
                  return l == NULL_LENGTH ? null : (l == 0 ? Constants.EMPTY_BYTE_ARRAY : readBytes(StringLengthDataType.STRING_FIXED, (int) l));
  
              case STRING_EOF:
                  return readBytes(StringLengthDataType.STRING_FIXED, this.payloadLength - this.position);
          }
          return null;
      }
  
      /**
       * Set position to next value in internal buffer skipping the current value according to {@link StringSelfDataType}.
       * 
       * @param type
       *            {@link StringSelfDataType}
       */
      public void skipBytes(StringSelfDataType type) {
          switch (type) {
              case STRING_TERM:
                  while ((this.position < this.payloadLength) && (this.byteBuffer[this.position] != 0)) {
                      this.position++;
                  }
                  this.position++; // skip terminating byte
                  break;
  
              case STRING_LENENC:
                  long len = readInteger(IntegerDataType.INT_LENENC);
                  if (len != NULL_LENGTH && len != 0) {
                      this.position += (int) len;
                  }
                  break;
  
              case STRING_EOF:
                  this.position = this.payloadLength;
                  break;
          }
      }
  
      /**
       * Read len bytes from internal buffer starting from current position into the new byte array.
       * 
       * @param type
       *            {@link StringLengthDataType}
       * @param len
       *            length
       * @return bytes
       */
      public byte[] readBytes(StringLengthDataType type, int len) {
          byte[] b;
          switch (type) {
              case STRING_FIXED:
              case STRING_VAR:
                  b = new byte[len];
                  System.arraycopy(this.byteBuffer, this.position, b, 0, len);
                  this.position += len;
                  return b;
          }
          return null;
      }
  
      /**
       * Read bytes from internal buffer starting from current position decoding them into String using the specified character encoding.
       * The length of data to read depends on {@link StringSelfDataType}.
       * 
       * @param type
       *            {@link StringSelfDataType}
       * @param encoding
       *            if null then platform default encoding is used
       * @return string
       */
      public String readString(StringSelfDataType type, String encoding) {
          String res = null;
          switch (type) {
              case STRING_TERM:
                  int i = this.position;
                  while ((i < this.payloadLength) && (this.byteBuffer[i] != 0)) {
                      i++;
                  }
                  res = readString(StringLengthDataType.STRING_FIXED, encoding, i - this.position);
                  this.position++; // skip terminating byte
                  break;
  
              case STRING_LENENC:
                  long l = readInteger(IntegerDataType.INT_LENENC);
                  return l == NULL_LENGTH ? null : (l == 0 ? "" : readString(StringLengthDataType.STRING_FIXED, encoding, (int) l));
  
              case STRING_EOF:
                  return readString(StringLengthDataType.STRING_FIXED, encoding, this.payloadLength - this.position);
  
          }
          return res;
      }
  
      /**
       * Read len bytes from internal buffer starting from current position decoding them into String using the specified character encoding.
       * 
       * @param type
       *            {@link StringLengthDataType}
       * @param encoding
       *            if null then platform default encoding is used
       * @param len
       *            length
       * @return string
       */
      public String readString(StringLengthDataType type, String encoding, int len) {
          String res = null;
          switch (type) {
              case STRING_FIXED:
              case STRING_VAR:
                  if ((this.position + len) > this.payloadLength) {
                      throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Buffer.1"));
                  }
  
                  res = StringUtils.toString(this.byteBuffer, this.position, len, encoding);
                  this.position += len;
                  break;
  
          }
          return res;
      }
  
      public static String extractSqlFromPacket(String possibleSqlQuery, NativePacketPayload packet, int endOfQueryPacketPosition, int maxQuerySizeToLog) {
          String extractedSql = null;
  
          if (possibleSqlQuery != null) {
              if (possibleSqlQuery.length() > maxQuerySizeToLog) {
                  StringBuilder truncatedQueryBuf = new StringBuilder(possibleSqlQuery.substring(0, maxQuerySizeToLog));
                  truncatedQueryBuf.append(Messages.getString("MysqlIO.25"));
                  extractedSql = truncatedQueryBuf.toString();
              } else {
                  extractedSql = possibleSqlQuery;
              }
          }
  
          if (extractedSql == null) {
              // This is probably from a client-side prepared statement
  
              int extractPosition = endOfQueryPacketPosition;
  
              boolean truncated = false;
  
              if (endOfQueryPacketPosition > maxQuerySizeToLog) {
                  extractPosition = maxQuerySizeToLog;
                  truncated = true;
              }
  
              extractedSql = StringUtils.toString(packet.getByteBuffer(), 1, (extractPosition - 1));
  
              if (truncated) {
                  extractedSql += Messages.getString("MysqlIO.25");
              }
          }
  
          return extractedSql;
      }
  
      /**
       * Tag current position with the given key for future reference.
       * 
       * @param key
       *            the position tag key name.
       * @return
       *         the previous value of this tag, if there was one, or -1.
       */
      public int setTag(String key) {
          Integer pos = this.tags.put(key, getPosition());
          return pos == null ? -1 : pos;
      }
  
      /**
       * Gets the value of the position tag for the given key.
       * 
       * @param key
       *            the position tag key name.
       * @return
       *         the position value of this tag, if there was one, or -1.
       */
      public int getTag(String key) {
          Integer pos = this.tags.get(key);
          return pos == null ? -1 : pos;
      }
  }
  