/** * <pre> * This program is free software; you can redistribute it and/or modify it under the terms of * the GNU AFFERO GENERAL PUBLIC LICENSE as published by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * 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 AFFERO GENERAL PUBLIC LICENSE for more details. * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE along with this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * </pre> */ package com.meidusa.amoeba.mysql.net.packet; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.sql.SQLException; import com.meidusa.amoeba.mysql.io.Constants; import com.meidusa.amoeba.mysql.io.MySqlPacketConstant; import com.meidusa.amoeba.mysql.util.MysqlStringUtil; import com.meidusa.amoeba.mysql.util.SingleByteCharsetConverter; import com.meidusa.amoeba.net.Connection; import com.meidusa.amoeba.net.DatabaseConnection; import com.meidusa.amoeba.net.packet.AbstractPacketBuffer; import com.meidusa.amoeba.util.StringUtil; /** * 该类负责 发送、接收socket输入流,并且可以根据包头信息,构造出ByteBuffer * * @author <a href=mailto:piratebase@sina.com>Struct chen</a> * @author hexianmao */ public class MysqlPacketBuffer extends AbstractPacketBuffer { static final int MAX_BYTES_TO_DUMP = 512; static final int NO_LENGTH_LIMIT = -1; static final long NULL_LENGTH = -1; protected boolean wasMultiPacket = false; private String charset; public static int getPacketLength(byte[] byteBuffer) { if (byteBuffer == null || byteBuffer.length < 4) { return 0; } else { int packetLength = (byteBuffer[0] & 0xff) + ((byteBuffer[1] & 0xff) << 8) + ((byteBuffer[2] & 0xff) << 16); return packetLength; } } public MysqlPacketBuffer(byte[] buf){ super(buf); position = MySqlPacketConstant.HEADER_SIZE; } public MysqlPacketBuffer(int size){ super(size); position = MySqlPacketConstant.HEADER_SIZE; } public void init(Connection conn) { super.init(conn); if(conn!= null){ charset = ((DatabaseConnection)conn).getCharset(); } } public String getCharset(){ return charset; } public int getBufLength() { return length; } public void setBufLength(int length) { this.length = length; } public int getPacketLength() { if (buffer == null || buffer.length < 4) { return 0; } else { int packetLength = (buffer[0] & 0xff) + ((buffer[1] & 0xff) << 8) + ((buffer[2] & 0xff) << 16); return packetLength; } } final void clear() { position = MySqlPacketConstant.HEADER_SIZE; } final void dump() { dump(length); } final String dump(int numBytes) { return StringUtil.dumpAsHex(getBytes(0, numBytes > length ? length : numBytes), numBytes > length ? length : numBytes); } final String dumpClampedBytes(int numBytes) { int numBytesToDump = numBytes < MAX_BYTES_TO_DUMP ? numBytes : MAX_BYTES_TO_DUMP; String dumped = StringUtil.dumpAsHex(getBytes(0, numBytesToDump > length ? length : numBytesToDump), numBytesToDump > length ? length : numBytesToDump); if (numBytesToDump < numBytes) { return dumped + " ....(packet exceeds max. dump length)"; } return dumped; } final void dumpHeader() { for (int i = 0; i < MySqlPacketConstant.HEADER_SIZE; i++) { String hexVal = Integer.toHexString(readByte(i) & 0xff); if (hexVal.length() == 1) { hexVal = "0" + hexVal; //$NON-NLS-1$ } System.out.print(hexVal + " "); //$NON-NLS-1$ } } final void dumpNBytes(int start, int nBytes) { StringBuffer asciiBuf = new StringBuffer(); for (int i = start; (i < (start + nBytes)) && (i < length); i++) { String hexVal = Integer.toHexString(readByte(i) & 0xff); if (hexVal.length() == 1) { hexVal = "0" + hexVal; //$NON-NLS-1$ } System.out.print(hexVal + " "); //$NON-NLS-1$ if ((readByte(i) > 32) && (readByte(i) < 127)) { asciiBuf.append((char) readByte(i)); } else { asciiBuf.append("."); //$NON-NLS-1$ } asciiBuf.append(" "); //$NON-NLS-1$ } System.out.println(" " + asciiBuf.toString()); //$NON-NLS-1$ } protected void ensureCapacity(int additionalData) { if ((position + additionalData) > length) { if (buffer.length <= (position + additionalData)) { int newLength = position + (int) (additionalData * 1.5) + 1; while (newLength < (position + additionalData + 1)) { newLength = (int) (newLength * 1.5); } byte[] newBytes = new byte[newLength]; System.arraycopy(buffer, 0, newBytes, 0, this.buffer.length); buffer = newBytes; } length = buffer.length; } } /** * Skip over a length-encoded string * * @return The position past the end of the string */ public int fastSkipLenString() { long len = this.readFieldLength(); position += len; return (int) len; } public void fastSkipLenByteArray() { long len = this.readFieldLength(); if (len == NULL_LENGTH || len == 0) { return; } position += len; } protected final byte[] getBufferSource() { return buffer; } /** * Returns the array of bytes this Buffer is using to read from. * * @return byte array being read from */ public byte[] getByteBuffer() { return buffer; } public final byte[] getBytes(int len) { byte[] b = new byte[len]; System.arraycopy(this.buffer, this.position, b, 0, len); this.position += len; // update cursor return b; } public byte[] getBytes(int offset, int len) { byte[] dest = new byte[len]; System.arraycopy(this.buffer, offset, dest, 0, len); return dest; } int getCapacity() { return this.buffer.length; } final boolean isLastDataPacket() { return ((length < 9) && ((this.buffer[4] & 0xff) == 254)); } final long newReadLength() { int sw = this.buffer[this.position++] & 0xff; switch (sw) { case 251: return 0; case 252: return readInt(); case 253: return readLongInt(); case 254: // changed for 64 bit lengths return readLongLong(); default: return sw; } } final long readFieldLength() { int i = this.buffer[this.position++] & 0xff; switch (i) { case 251: return NULL_LENGTH; case 252: return readInt(); case 253: return readLongInt(); case 254: return readLongLong(); default: return i; } } final int readInt() { byte[] b = this.buffer; // a little bit optimization return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8); } final int readIntAsLong() { byte[] b = this.buffer; return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8) | ((b[this.position++] & 0xff) << 16) | ((b[this.position++] & 0xff) << 24); } final byte[] readLenByteArray(int offset) { long len = this.readFieldLength(); if (len == NULL_LENGTH) { return null; } if (len == 0) { return Constants.EMPTY_BYTE_ARRAY; } this.position += offset; return getBytes((int) len); } final long readLength() { int sw = this.buffer[this.position++] & 0xff; switch (sw) { case 251: return 0; case 252: return readInt(); case 253: return readLongInt(); case 254: return readLong(); default: return sw; } } final long readLong() { byte[] b = this.buffer; return ((long) b[this.position++] & 0xff) | (((long) b[this.position++] & 0xff) << 8) | ((long) (b[this.position++] & 0xff) << 16) | ((long) (b[this.position++] & 0xff) << 24); } final int readLongInt() { byte[] b = this.buffer; return (b[this.position++] & 0xff) | ((b[this.position++] & 0xff) << 8) | ((b[this.position++] & 0xff) << 16); } final long readLongLong() { byte[] b = this.buffer; 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); } final int readnBytes() { int sw = this.buffer[this.position++] & 0xff; switch (sw) { case 1: return this.buffer[this.position++] & 0xff; case 2: return this.readInt(); case 3: return this.readLongInt(); case 4: return (int) this.readLong(); default: return 255; } } final String readString() { int i = position; int len = 0; int maxLen = length; while ((i < maxLen) && (buffer[i] != 0)) { len++; i++; } String s = new String(buffer, position, len); position += (len + 1); // update cursor return s; } final String readLengthCodedString(String encoding) { int fieldLength = (int) readFieldLength(); if (fieldLength <= 0) { return null; } try { if (encoding != null) { return new String(buffer, position, fieldLength, encoding); } else { return new String(buffer, position, fieldLength); } } catch (UnsupportedEncodingException e) { // TODO handle exception return new String(buffer, position, fieldLength); } finally { position += fieldLength; // update cursor } } public static boolean isErrorPacket(byte[] bty) { return isPacketType(bty, (byte) 0xff); } /** * <PRE> * VERSION 4.0 * Bytes Name * ----- ---- * 1 field_count, always = 0xfe * * VERSION 4.1 * Bytes Name * ----- ---- * 1 field_count, always = 0xfe * 2 warning_count * 2 Status Flags * * field_count: The value is always 0xfe (decimal 254). * However ... recall (from the * section "Elements", above) that the value 254 can begin * a Length-Encoded-Binary value which contains an 8-byte * integer. So, to ensure that a packet is really an EOF * Packet: (a) check that first byte in packet = 0xfe, (b) * check that size of packet < 9. * * warning_count: Number of warnings. Sent after all data has been sent * to the client. * * server_status: Contains flags like SERVER_MORE_RESULTS_EXISTS * </PRE> * @param bty * @return */ public static boolean isEofPacket(byte[] bty) { return bty.length< 13 && isPacketType(bty, (byte) 0xfe); } public static int increasePacketId(int packetId) { if (packetId >= 255) { return 0; } else { return packetId++; } } public static boolean isPacketType(byte[] bytes, byte type) { if (bytes.length >= 5) { return bytes[4] == type; } return false; } public static boolean isOkPacket(byte[] bty) { return isPacketType(bty, (byte) 0x00); } final String readString(String encoding) { int i = position; int len = 0; int maxLen = length; while ((i < maxLen) && (buffer[i] != 0)) { len++; i++; } try { return new String(buffer, position, len, encoding); } catch (UnsupportedEncodingException e) { // TODO handle exception return new String(buffer, position, len).trim(); } finally { position += (len + 1); // update cursor } } /** * Sets the array of bytes to use as a buffer to read from. * * @param buffer the array of bytes to use as a buffer */ public void setByteBuffer(byte[] byteBufferToSet) { this.buffer = byteBufferToSet; } /** * Sets whether this packet was part of a multipacket * * @param flag was this packet part of a multipacket? */ public void setWasMultiPacket(boolean flag) { this.wasMultiPacket = flag; } public String toString() { return dumpClampedBytes(getPosition()); } public String toSuperString() { return super.toString(); } /** * Was this packet part of a multipacket? * * @return was this packet part of a multipacket? */ public boolean wasMultiPacket() { return this.wasMultiPacket; } // Write a byte array final void writeBytesNoNull(byte[] bytes) { int len = bytes.length; ensureCapacity(len); System.arraycopy(bytes, 0, this.buffer, this.position, len); this.position += len; } // Write a byte array with the given offset and length final void writeBytesNoNull(byte[] bytes, int offset, int length) throws SQLException { ensureCapacity(length); System.arraycopy(bytes, offset, this.buffer, this.position, length); this.position += length; } public final void writeDouble(double d) { long l = Double.doubleToLongBits(d); writeLongLong(l); } public double readDouble() { long result = readLongLong(); return Double.longBitsToDouble(result); } public final void writeFieldLength(long length) { if (length < 251) { writeByte((byte) length); } else if (length < 65536L) { ensureCapacity(3); writeByte((byte) 252); writeInt((int) length); } else if (length < 16777216L) { ensureCapacity(4); writeByte((byte) 253); writeLongInt((int) length); } else { ensureCapacity(9); writeByte((byte) 254); writeLongLong(length); } } public final void writeFloat(float f) { ensureCapacity(4); int i = Float.floatToIntBits(f); byte[] b = this.buffer; b[this.position++] = (byte) (i & 0xff); b[this.position++] = (byte) (i >>> 8); b[this.position++] = (byte) (i >>> 16); b[this.position++] = (byte) (i >>> 24); } final float readFloat() { byte[] b = this.buffer; int result = ((int) b[this.position++] & 0xff) | (((int) b[this.position++] & 0xff) << 8) | ((int) (b[this.position++] & 0xff) << 16) | ((int) (b[this.position++] & 0xff) << 24); return Float.intBitsToFloat(result); } // 2000-06-05 Changed final void writeInt(int i) { ensureCapacity(2); byte[] b = this.buffer; b[this.position++] = (byte) (i & 0xff); b[this.position++] = (byte) (i >>> 8); } // Write a String using the specified character encoding final void writeLenBytes(byte[] b) { int len = b.length; ensureCapacity(len + 9); writeFieldLength(len); System.arraycopy(b, 0, this.buffer, this.position, len); this.position += len; } public final void writeLengthCodedString(String s, String encoding) { if (s != null) { byte[] b; try { b = s.getBytes(encoding); } catch (UnsupportedEncodingException e) { // TODO handle exception e.printStackTrace(); b = s.getBytes(); } ensureCapacity(b.length + 9); this.writeFieldLength(b.length); this.writeBytesNoNull(b); } else { this.writeByte((byte) 0); } } // Write a String using the specified character encoding final void writeLenString(String s, String encoding, String serverEncoding, SingleByteCharsetConverter converter, boolean parserKnowsUnicode) throws UnsupportedEncodingException { byte[] b = null; if (converter != null) { b = converter.toBytes(s); } else { b = MysqlStringUtil.getBytes(s, converter, encoding, serverEncoding, parserKnowsUnicode); } int len = b.length; ensureCapacity(len + 9); writeFieldLength(len); System.arraycopy(b, 0, this.buffer, this.position, len); this.position += len; } final void writeLong(long i) { ensureCapacity(4); byte[] b = this.buffer; b[this.position++] = (byte) (i & 0xff); b[this.position++] = (byte) (i >>> 8); b[this.position++] = (byte) (i >>> 16); b[this.position++] = (byte) (i >>> 24); } final void writeLongInt(int i) { ensureCapacity(3); byte[] b = this.buffer; b[this.position++] = (byte) (i & 0xff); b[this.position++] = (byte) (i >>> 8); b[this.position++] = (byte) (i >>> 16); } final void writeLongLong(long i) { ensureCapacity(8); byte[] b = this.buffer; b[this.position++] = (byte) (i & 0xff); b[this.position++] = (byte) (i >>> 8); b[this.position++] = (byte) (i >>> 16); b[this.position++] = (byte) (i >>> 24); b[this.position++] = (byte) (i >>> 32); b[this.position++] = (byte) (i >>> 40); b[this.position++] = (byte) (i >>> 48); b[this.position++] = (byte) (i >>> 56); } // Write null-terminated string final void writeString(String s) { ensureCapacity((s.length() * 2) + 1); writeStringNoNull(s); this.buffer[this.position++] = 0; } // Write null-terminated string in the given encoding final void writeString(String s, String encoding) throws UnsupportedEncodingException { ensureCapacity((s.length() * 2) + 1); writeStringNoNull(s, encoding, encoding, false); this.buffer[this.position++] = 0; } // Write string, with no termination final void writeStringNoNull(String s) { int len = s.length(); ensureCapacity(len * 2); System.arraycopy(s.getBytes(), 0, this.buffer, this.position, len); this.position += len; } // Write a String using the specified character encoding final void writeStringNoNull(String s, String encoding, String serverEncoding, boolean parserKnowsUnicode) throws UnsupportedEncodingException { byte[] b = null; SingleByteCharsetConverter converter = SingleByteCharsetConverter.getInstance(encoding); b = MysqlStringUtil.getBytes(s, converter, encoding, serverEncoding, parserKnowsUnicode); int len = b.length; ensureCapacity(len); System.arraycopy(b, 0, this.buffer, this.position, len); this.position += len; } // 将从0当到前位置的所有字节写入到 ByteBuffer中,并且将 ByteBuffer position设置到0 public ByteBuffer toByteBuffer() { ByteBuffer buffer = ByteBuffer.allocate(this.getPacketLength() + 4); buffer.put(this.buffer, 0, this.getPacketLength() + 4); buffer.rewind(); return buffer; } }