/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* 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 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;
private int
payloadLength = 0;
private byte[]
byteBuffer;
private int
position = 0;
static final int
MAX_BYTES_TO_DUMP = 1024;
@
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 http://dev.mysql.com/doc/internals/en/packet-EOF_Packet.html
*
* @return true if it is a EOF packet
*/
public final boolean
isEOFPacket() {
return (this.
byteBuffer[0] & 0xff) ==
TYPE_ID_EOF && (
getPayloadLength() <= 5);
}
/**
* Is it a Protocol::AuthSwitchRequest packet.
* See http://dev.mysql.com/doc/internals/en/connection-phase-packets.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 http://dev.mysql.com/doc/internals/en/packet-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 http://dev.mysql.com/doc/internals/en/packet-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 && (
getPayloadLength() < 16777215);
}
/**
* Is it a Protocol::AuthMoreData packet.
* See http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthMoreData
*
* @return true if it is a Protocol::AuthMoreData packet
*/
public final boolean
isAuthMoreData() {
return ((this.
byteBuffer[0] & 0xff) == 1);
}
/**
* 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;
}
}