com.mysql.cj.protocol.a.CompressedPacketSender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mysql-connector-java Show documentation
Show all versions of mysql-connector-java Show documentation
Fixed bugs repo for official MySQL JDBC connector
The newest version!
/*
* Copyright (c) 2015, 2020, 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.io.BufferedOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import com.mysql.cj.protocol.MessageSender;
/**
* A {@link MessageSender} for the compressed protocol.
*
* TODO: add support for pre-allocated buffer for large packets (if there's a demonstrable perf improvement)
*/
public class CompressedPacketSender implements MessageSender {
private BufferedOutputStream outputStream;
private Deflater deflater = new Deflater();
/** Buffer to compress data to. Used only across one send() invocation. */
private byte compressedPacket[];
/** Sequence id of compressed packet. Used only across one send() invocation. */
private byte compressedSequenceId = 0;
/** Length of current compressed packet. */
private int compressedPayloadLen = 0;
public static final int COMP_HEADER_LENGTH = 7;
public static final int MIN_COMPRESS_LEN = 50;
public CompressedPacketSender(BufferedOutputStream outputStream) {
this.outputStream = outputStream;
}
/**
* Shut down this packet sender and deallocate any resources.
*/
public void stop() {
this.deflater.end();
this.deflater = null;
}
private void resetPacket() {
this.compressedPayloadLen = 0;
this.deflater.reset();
}
/**
* Add and compress the header for the raw packet into the compressed packet.
*
* @param packetSequence
* sequence id
* @param uncompressedPacketLen
* uncompressed packet length
*/
private void addUncompressedHeader(byte packetSequence, int uncompressedPacketLen) {
byte uncompressedHeader[] = new byte[NativeConstants.HEADER_LENGTH];
NativeUtils.encodeMysqlThreeByteInteger(uncompressedPacketLen, uncompressedHeader, 0);
uncompressedHeader[3] = packetSequence;
this.deflater.setInput(uncompressedHeader);
this.compressedPayloadLen += this.deflater.deflate(this.compressedPacket, this.compressedPayloadLen,
this.compressedPacket.length - this.compressedPayloadLen);
}
/**
* Add and compress the payload into the compressed packet.
*
* @param payload
* payload bytes
* @param payloadOffset
* offset
* @param payloadLen
* length
*/
private void addPayload(byte[] payload, int payloadOffset, int payloadLen) {
this.deflater.setInput(payload, payloadOffset, payloadLen);
this.compressedPayloadLen += this.deflater.deflate(this.compressedPacket, this.compressedPayloadLen,
this.compressedPacket.length - this.compressedPayloadLen);
}
/**
* Complete compression of the current payload contents to the compressed packet.
*/
private void completeCompression() {
this.deflater.finish();
this.compressedPayloadLen += this.deflater.deflate(this.compressedPacket, this.compressedPayloadLen,
this.compressedPacket.length - this.compressedPayloadLen);
}
/**
* Write the compressed packet header.
*
* @param compLen
* compressed data length
* @param seq
* sequence id
* @param uncompLen
* uncompressed data length
* @throws IOException
* if write exception occurs
*/
private void writeCompressedHeader(int compLen, byte seq, int uncompLen) throws IOException {
this.outputStream.write(NativeUtils.encodeMysqlThreeByteInteger(compLen));
this.outputStream.write(seq);
this.outputStream.write(NativeUtils.encodeMysqlThreeByteInteger(uncompLen));
}
/**
* Write an uncompressed packet header.
*
* @param packetLen
* packet length
* @param packetSequence
* sequence id
* @throws IOException
* if write exception occurs
*/
private void writeUncompressedHeader(int packetLen, byte packetSequence) throws IOException {
this.outputStream.write(NativeUtils.encodeMysqlThreeByteInteger(packetLen));
this.outputStream.write(packetSequence);
}
/**
* Send a compressed packet.
*
* @param uncompressedPayloadLen
* uncompressed data length
* @throws IOException
* if write exception occurs
*/
private void sendCompressedPacket(int uncompressedPayloadLen) throws IOException {
writeCompressedHeader(this.compressedPayloadLen, this.compressedSequenceId++, uncompressedPayloadLen);
// compressed payload
this.outputStream.write(this.compressedPacket, 0, this.compressedPayloadLen);
}
/**
* Packet sender implementation for the compressed MySQL protocol. For compressed transmission of multi-packets, split the packets up in the same way as the
* uncompressed protocol. We fit up to MAX_PACKET_SIZE bytes of split uncompressed packet, including the header, into an compressed packet. The first packet
* of the multi-packet is 4 bytes of header and MAX_PACKET_SIZE - 4 bytes of the payload. The next packet must send the remaining four bytes of the payload
* followed by a new header and payload. If the second split packet is also around MAX_PACKET_SIZE in length, then only MAX_PACKET_SIZE - 4 (from the
* previous packet) - 4 (for the new header) can be sent. This means the payload will be limited by 8 bytes and this will continue to increase by 4 at every
* iteration.
*
* @param packet
* data bytes
* @param packetLen
* packet length
* @param packetSequence
* sequence id
* @throws IOException
* if i/o exception occurs
*/
public void send(byte[] packet, int packetLen, byte packetSequence) throws IOException {
this.compressedSequenceId = packetSequence;
// short-circuit send small packets without compression and return
if (packetLen < MIN_COMPRESS_LEN) {
writeCompressedHeader(packetLen + NativeConstants.HEADER_LENGTH, this.compressedSequenceId, 0);
writeUncompressedHeader(packetLen, packetSequence);
this.outputStream.write(packet, 0, packetLen);
this.outputStream.flush();
return;
}
if (packetLen + NativeConstants.HEADER_LENGTH > NativeConstants.MAX_PACKET_SIZE) {
this.compressedPacket = new byte[NativeConstants.MAX_PACKET_SIZE];
} else {
this.compressedPacket = new byte[NativeConstants.HEADER_LENGTH + packetLen];
}
PacketSplitter packetSplitter = new PacketSplitter(packetLen);
int unsentPayloadLen = 0;
int unsentOffset = 0;
// loop over constructing and sending compressed packets
while (true) {
this.compressedPayloadLen = 0;
if (packetSplitter.nextPacket()) {
// rest of previous packet
if (unsentPayloadLen > 0) {
addPayload(packet, unsentOffset, unsentPayloadLen);
}
// current packet
int remaining = NativeConstants.MAX_PACKET_SIZE - unsentPayloadLen;
// if remaining is 0 then we are sending a very huge packet such that are 4-byte header-size carryover from last packet accumulated to the size
// of a whole packet itself. We don't handle this. Would require 4 million packet segments (64 gigs in one logical packet)
int len = Math.min(remaining, NativeConstants.HEADER_LENGTH + packetSplitter.getPacketLen());
int lenNoHdr = len - NativeConstants.HEADER_LENGTH;
addUncompressedHeader(packetSequence, packetSplitter.getPacketLen());
addPayload(packet, packetSplitter.getOffset(), lenNoHdr);
completeCompression();
// don't send payloads with incompressible data
if (this.compressedPayloadLen >= len) {
// combine the unsent and current packet in an uncompressed packet
writeCompressedHeader(unsentPayloadLen + len, this.compressedSequenceId++, 0);
this.outputStream.write(packet, unsentOffset, unsentPayloadLen);
writeUncompressedHeader(lenNoHdr, packetSequence);
this.outputStream.write(packet, packetSplitter.getOffset(), lenNoHdr);
} else {
sendCompressedPacket(len + unsentPayloadLen);
}
packetSequence++;
unsentPayloadLen = packetSplitter.getPacketLen() - lenNoHdr;
unsentOffset = packetSplitter.getOffset() + lenNoHdr;
resetPacket();
} else if (unsentPayloadLen > 0) {
// no more packets, send remaining unsent data
addPayload(packet, unsentOffset, unsentPayloadLen);
completeCompression();
if (this.compressedPayloadLen >= unsentPayloadLen) {
writeCompressedHeader(unsentPayloadLen, this.compressedSequenceId, 0);
this.outputStream.write(packet, unsentOffset, unsentPayloadLen);
} else {
sendCompressedPacket(unsentPayloadLen);
}
resetPacket();
break;
} else {
// nothing left to send (only happens on boundaries)
break;
}
}
this.outputStream.flush();
// release reference to (possibly large) compressed packet buffer
this.compressedPacket = null;
}
@Override
public MessageSender undecorateAll() {
return this;
}
@Override
public MessageSender undecorate() {
return this;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy