com.mysql.cj.protocol.a.NativePacketPayload Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mysql-connector-j Show documentation
Show all versions of mysql-connector-j Show documentation
JDBC Type 4 driver for MySQL.
/*
* Copyright (c) 2002, 2024, 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 designed to work with certain software 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 either included with the program or referenced in the documentation.
*
* 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 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:
if (this.position >= this.payloadLength) { // Servers do not always respect STRING_LENENC encoding: Bug#35630063.
return "";
}
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;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy