Java tutorial
/*------------------------------------------------------------------------------------------------- _______ __ _ _______ _______ ______ ______ |_____| | \ | | |______ | \ |_____] | | | \_| | ______| |_____/ |_____] Copyright (c) 2016, antsdb.com and/or its affiliates. All rights reserved. *-xguo0<@ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License, version 3, as published by the Free Software Foundation. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt> -------------------------------------------------------------------------------------------------*/ package com.antsdb.saltedfish.server.mysql; import io.netty.buffer.ByteBuf; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.time.Duration; import org.apache.commons.codec.Charsets; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.time.DurationFormatUtils; import org.apache.commons.lang.time.FastDateFormat; import org.slf4j.Logger; import static com.antsdb.saltedfish.server.mysql.MysqlConstant.*; import com.antsdb.saltedfish.cpp.FishBool; import com.antsdb.saltedfish.cpp.FishObject; import com.antsdb.saltedfish.cpp.Float4; import com.antsdb.saltedfish.cpp.Float8; import com.antsdb.saltedfish.cpp.Int4; import com.antsdb.saltedfish.cpp.Int8; import com.antsdb.saltedfish.cpp.Value; import com.antsdb.saltedfish.server.mysql.packet.MySQLPacket; import com.antsdb.saltedfish.server.mysql.util.BufferUtils; import com.antsdb.saltedfish.sql.vdm.FieldMeta; import com.antsdb.saltedfish.sql.DataType; import com.antsdb.saltedfish.sql.Session; import com.antsdb.saltedfish.sql.vdm.CursorMeta; import com.antsdb.saltedfish.sql.vdm.Record; import com.antsdb.saltedfish.sql.vdm.Transaction; import com.antsdb.saltedfish.util.Callback; import com.antsdb.saltedfish.util.UberUtil; /** * * This class encode data into packet and * @author roger */ public final class PacketEncoder { static Logger _log = UberUtil.getThisLogger(); // for a simple OK packet, no need to call back, just out put hard coded bytes public static final byte[] OK_PACKET = new byte[] { 7, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0 }; // for a auth OK packet, no need to call back, just out put hard coded bytes public static final byte[] AUTH_OK_PACKET = new byte[] { 7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0 }; static final FastDateFormat TIMESTAMP19_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); static final FastDateFormat TIMESTAMP29_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS000000"); private Charset cs; private int csidx; /** * Add header to finish the full packet * @param out * @param packetSeq * @param writeBodyFunc write packet body function */ public static void writePacket(ByteBuf out, byte packetSeq, Callback writeBodyFunc) { int start = out.writerIndex(); out.writeZero(4); writeBodyFunc.callback(); int end = out.writerIndex(); out.writeByte(0); out.writerIndex(start); int length = end - start - MySQLPacket.packetHeaderSize; BufferUtils.writeLongInt(out, length); out.writeByte(packetSeq); out.writerIndex(end); if (_log.isTraceEnabled()) { int readerIndex = out.readerIndex(); out.readerIndex(start); byte[] bytes = new byte[end - start]; out.readBytes(bytes); out.readerIndex(readerIndex); String dump = '\n' + UberUtil.hexDump(bytes); _log.trace(dump); } } /** * Writer com_stmt_prepare_response packet body * <pre> * From server to client, in response to prepared statement initialization packet. * It is made up of: * 1.a PREPARE_OK packet * 2.if "number of parameters" > 0 * (field packets) as in a Result Set Header Packet * (EOF packet) * 3.if "number of columns" > 0 * (field packets) as in a Result Set Header Packet * (EOF packet) * * ----------------------------------------------------------------------------------------- * * Bytes Name * ----- ---- * 1 0 - marker for OK packet * 4 statement_handler_id * 2 number of columns in result set * 2 number of parameters in query * 1 filler (always 0) * 2 warning count * * @see http://dev.mysql.com/doc/internals/en/prepared-statement-initialization-packet.html * </pre> * @param buffer * @param statementId * @param columnsNumber * @param parametersNumber */ public void writePreparedOKBody(ByteBuf buffer, long statementId, int columnsNumber, int parametersNumber) { // flag = 0 buffer.writeByte(0); BufferUtils.writeUB4(buffer, statementId); BufferUtils.writeUB2(buffer, columnsNumber); BufferUtils.writeUB2(buffer, parametersNumber); // filler = 0 buffer.writeByte(0); // warningCount = 0; BufferUtils.writeUB2(buffer, 0); } /** * * From server to client after command, if no error and result set -- that is, * if the command was a query which returned a result set. The Result Set Header * Packet is the first of several, possibly many, packets that the server sends * for result sets. The order of packets for a result set is: * * <pre> * (Result Set Header Packet) the number of columns * (Field Packets) column descriptors * (EOF Packet) marker: end of Field Packets * (Row Data Packets) row contents * (EOF Packet) marker: end of Data Packets * * Bytes Name * ----- ---- * 1-9 (Length-Coded-Binary) field_count * 1-9 (Length-Coded-Binary) extra * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Result_Set_Header_Packet * </pre> * * @param buffer * @param meta */ public void writeColumnDefBody(ByteBuf buffer, FieldMeta meta) { //catalog BufferUtils.writeLenString(buffer, "def", this.cs); //db, schema if (meta.getSourceTable() != null) { BufferUtils.writeLenString(buffer, meta.getSourceTable().getNamespace(), this.cs); } else { BufferUtils.writeLenString(buffer, "", this.cs); } // table BufferUtils.writeLenString(buffer, meta.getTableAlias(), this.cs); // orgTable if (meta.getSourceTable() != null) { BufferUtils.writeLenString(buffer, meta.getSourceTable().getTableName(), this.cs); } else { BufferUtils.writeLenString(buffer, "", this.cs); } // col name BufferUtils.writeLenString(buffer, meta.getName(), this.cs); // col original name BufferUtils.writeLenString(buffer, meta.getSourceName(), this.cs); // next length buffer.writeByte((byte) 0x0C); if (meta.getType() == null) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, 0); buffer.writeByte((byte) (FIELD_TYPE_NULL & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte(0); } else if (meta.getType().getJavaType() == Boolean.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_TINY & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte(0); } else if (meta.getType().getSqlType() == Types.TINYINT) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_TINY & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else if (meta.getType().getJavaType() == String.class) { // char set utf8_general_ci : 0x21 BufferUtils.writeInt(buffer, this.csidx); // length BufferUtils.writeUB4(buffer, meta.getType().getLength() * 3); // type code buffer.writeByte((byte) (FIELD_TYPE_VAR_STRING & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte(0); } else if (meta.getType().getJavaType() == Integer.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, 21); buffer.writeByte((byte) (FIELD_TYPE_LONG & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte(0); } else if (meta.getType().getJavaType() == Long.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, 21); buffer.writeByte((byte) (FIELD_TYPE_LONGLONG & 0xff)); buffer.writeByte(0x00); // signed. buffer.writeByte(0x00); buffer.writeByte(0); } else if (meta.getType().getJavaType() == BigInteger.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_LONGLONG & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte(0); } else if (meta.getType().getJavaType() == BigDecimal.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_DECIMAL & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else if (meta.getType().getJavaType() == Float.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_FLOAT & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else if (meta.getType().getJavaType() == Double.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_DOUBLE & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else if (meta.getType().getJavaType() == Timestamp.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_TIMESTAMP & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else if (meta.getType().getJavaType() == Date.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_DATE & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else if (meta.getType().getJavaType() == Time.class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, meta.getType().getLength()); buffer.writeByte((byte) (FIELD_TYPE_TIME & 0xff)); buffer.writeByte(0x00); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } // BLOB return byte[] as its java type else if (meta.getType().getJavaType() == byte[].class) { BufferUtils.writeInt(buffer, 0x3f); BufferUtils.writeUB4(buffer, 2147483647); buffer.writeByte((byte) (FIELD_TYPE_BLOB & 0xff)); // flag for Blob is x90 x00 buffer.writeByte(0x90); buffer.writeByte(0x00); buffer.writeByte((byte) meta.getType().getScale()); } else { throw new NotImplementedException("Unsupported data type:" + meta.getType().getJavaType()); } /** * need add meta info to specify flag if (meta.isNullable() == 1) { flags |= 0001; } if (meta.isSigned()) { flags |= 0020; } if (meta.isAutoIncrement()) { flags |= 0200; } */ // filler buffer.writeZero(2); } /** * From server to client after command, if no error and result set -- that is, * if the command was a query which returned a result set. The Result Set Header * Packet is the first of several, possibly many, packets that the server sends * for result sets. The order of packets for a result set is: * * <pre> * (Result Set Header Packet) the number of columns * (Field Packets) column descriptors * (EOF Packet) marker: end of Field Packets * (Row Data Packets) row contents * (EOF Packet) marker: end of Data Packets * * Bytes Name * ----- ---- * 1-9 (Length-Coded-Binary) field_count * 1-9 (Length-Coded-Binary) extra * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Result_Set_Header_Packet * </pre> * * @param buffer * @param fieldCount */ public void writeResultSetHeaderBody(ByteBuf buffer, int fieldCount) { // field count BufferUtils.writeLength(buffer, fieldCount); } /** * * From server to client. One packet for each row in the result set. * * <pre> * Bytes Name * ----- ---- * n (Length Coded String) (column value) * ... * * (column value): The data in the column, as a character string. * If a column is defined as non-character, the * server converts the value into a character * before sending it. Since the value is a Length * Coded String, a NULL can be represented with a * single byte containing 251(see the description * of Length Coded Strings in section "Elements" above). * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Row_Data_Packet * </pre> * * @param buffer * @param fieldValues */ public void writeRowBinaryBody(ByteBuf buffer, long pRecord, CursorMeta meta, int nColumns) { if ((pRecord != 0) && (nColumns > 0)) { // start of package buffer.writeByte(0); int nullByteCnt = (nColumns + 7 + 2) / 8; byte[] nullBitmap = new byte[nullByteCnt]; int nullPos = buffer.writerIndex(); buffer.writeBytes(nullBitmap); for (int i = 0; i < nColumns; i++) { long pValue = Record.getValueAddress(pRecord, i); if (pValue != 0) { writeValue(buffer, meta.getColumn(i), pValue); } else { nullBitmap[(i + 2) / 8] |= 1 << (i + 2) % 8; } } int endPos = buffer.writerIndex(); buffer.writerIndex(nullPos); buffer.writeBytes(nullBitmap); buffer.writerIndex(endPos); } } private void writeValue(ByteBuf buffer, FieldMeta meta, long pValue) { if (writeValueFast(buffer, meta, pValue)) { return; } writeValueSlow(buffer, meta, pValue); } private boolean writeValueFast(ByteBuf buffer, FieldMeta meta, long pValue) { DataType type = meta.getType(); byte format = Value.getFormat(null, pValue); if (type.getSqlType() == Types.TINYINT) { if (format == Value.FORMAT_INT4) { buffer.writeByte(Int4.get(pValue)); return true; } else if (format == Value.FORMAT_INT8) { buffer.writeByte((int) Int8.get(null, pValue)); return true; } } else if (type.getJavaType() == Boolean.class) { boolean b = FishBool.get(null, pValue); buffer.writeByte(b ? 1 : 0); return true; } else if (type.getJavaType() == Integer.class) { if (format == Value.FORMAT_INT4) { BufferUtils.writeUB4(buffer, Int4.get(pValue)); return true; } else if (format == Value.FORMAT_INT8) { BufferUtils.writeUB4(buffer, (int) Int8.get(null, pValue)); return true; } } else if (type.getJavaType() == Long.class) { if (format == Value.FORMAT_INT4) { BufferUtils.writeLongLong(buffer, Int4.get(pValue)); return true; } else if (format == Value.FORMAT_INT8) { BufferUtils.writeLongLong(buffer, Int8.get(null, pValue)); return true; } } else if (type.getJavaType() == Float.class) { if (format == Value.FORMAT_FLOAT4) { BufferUtils.writeUB4(buffer, Float.floatToIntBits(Float4.get(null, pValue))); return true; } } else if (type.getJavaType() == Double.class) { if (format == Value.FORMAT_FLOAT4) { BufferUtils.writeLongLong(buffer, Double.doubleToLongBits(Float8.get(null, pValue))); return true; } } return false; } private void writeValueSlow(ByteBuf buffer, FieldMeta meta, long pValue) { Object value = FishObject.get(null, pValue); DataType type = meta.getType(); if (type.getSqlType() == Types.TINYINT) { buffer.writeByte((Integer) value); } else if (type.getJavaType() == Integer.class) { BufferUtils.writeUB4(buffer, (Integer) value); } else if (type.getJavaType() == Long.class) { BufferUtils.writeLongLong(buffer, (Long) value); } else if (type.getJavaType() == Float.class) { BufferUtils.writeUB4(buffer, Float.floatToIntBits((Float) value)); } else if (type.getJavaType() == Double.class) { BufferUtils.writeLongLong(buffer, Double.doubleToLongBits((Double) value)); } else if (type.getJavaType() == Timestamp.class) { BufferUtils.writeTimestamp(buffer, (Timestamp) value); } else if (type.getJavaType() == Date.class) { BufferUtils.writeDate(buffer, (Date) value); } else if (type.getJavaType() == byte[].class) { BufferUtils.writeWithLength(buffer, (byte[]) value); } else { BufferUtils.writeLenString(buffer, value.toString(), this.cs); } } /** * * From server to client. One packet for each row in the result set. * * <pre> * Bytes Name * ----- ---- * n (Length Coded String) (column value) * ... * * (column value): The data in the column, as a character string. * If a column is defined as non-character, the * server converts the value into a character * before sending it. Since the value is a Length * Coded String, a NULL can be represented with a * single byte containing 251(see the description * of Length Coded Strings in section "Elements" above). * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Row_Data_Packet * </pre> * * @param buffer * @param nColumns * @param rowRec */ public void writeRowTextBody(ByteBuf buffer, long pRecord, int nColumns) { for (int i = 0; i < nColumns; i++) { Object fv = Record.getValue(pRecord, i); if (fv instanceof Boolean) { // mysql has no boolean it is actually tinyint fv = ((Boolean) fv) ? 1 : 0; } if (fv == null) { // null mark is 251 buffer.writeByte((byte) 251); } else if (fv instanceof Duration) { Duration t = (Duration) fv; String text = DurationFormatUtils.formatDuration(t.toMillis(), "HH:mm:ss"); BufferUtils.writeLenString(buffer, text, this.cs); } else if (fv instanceof Timestamp) { // @see ResultSetRow#getDateFast, mysql jdbc driver only take precision 19,21,29 if callers wants // to get a Date from a datetime column Timestamp ts = (Timestamp) fv; if (ts.getTime() == Long.MIN_VALUE) { // mysql '0000-00-00 00:00:00' is treated as null in jdbc buffer.writeByte((byte) 251); } else { String text; if (ts.getNanos() == 0) { text = TIMESTAMP19_FORMAT.format(ts); } else { text = TIMESTAMP29_FORMAT.format(ts); } BufferUtils.writeLenString(buffer, text, this.cs); } } else if (fv instanceof byte[]) { BufferUtils.writeWithLength(buffer, (byte[]) fv); } else if ((fv instanceof Date) && (((Date) fv).getTime() == Long.MIN_VALUE)) { // mysql '0000-00-00' is treated as null in jdbc buffer.writeByte((byte) 251); } else { String val = fv.toString(); if (val.length() == 0) { // empty mark is 0 buffer.writeByte((byte) 0); } else { BufferUtils.writeLenString(buffer, val, this.cs); } } } } /** * * From server to client during initial handshake. * * <pre> * Bytes Name * ----- ---- * 1 protocol_version * n (Null-Terminated String) server_version * 4 thread_id * 8 scramble_buff * 1 (filler) always 0x00 * 2 server_capabilities * 1 server_language * 2 server_status * 13 (filler) always 0x00 ... * 13 rest of scramble_buff (4.1) * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Handshake_Initialization_Packet * </pre> * * @param buffer * @param serverVersion * @param protocolVersion * @param threadId * @param capability * @param charSet * @param status */ public static void writeHandshakeBody(ByteBuf buffer, String serverVersion, byte protocolVersion, long threadId, int capability, byte charSet, int status) { buffer.writeByte(protocolVersion); BufferUtils.writeString(buffer, serverVersion); BufferUtils.writeUB4(buffer, threadId); // seed byte[] seed = new byte[] { 0x50, 0x3a, 0x6e, 0x3d, 0x25, 0x40, 0x51, 0x56 }; buffer.writeBytes(seed); buffer.writeByte(0); // lower 16 bits of sever capacity BufferUtils.writeInt(buffer, capability); // serverCharsetIndex buffer.writeByte(charSet); // server status BufferUtils.writeInt(buffer, status); // upper 16 bits of server capacity BufferUtils.writeInt(buffer, capability >>> 16); // plugin data length if ((capability & MysqlServerHandler.CLIENT_PLUGIN_AUTH) != 0) { buffer.writeByte(0x15); } else { buffer.writeByte(0); } // fill the rest 10 bytes with 0 buffer.writeZero(10); if ((capability & MysqlServerHandler.CLIENT_PLUGIN_AUTH) != 0) { // no idea what this means, copied from trace buffer.writeBytes( new byte[] { 0x73, 0x68, 0x2f, 0x50, 0x27, 0x6f, 0x7a, 0x38, 0x46, 0x38, 0x26, 0x51, 0x00 }); BufferUtils.writeString(buffer, MysqlServerHandler.AUTH_MYSQL_NATIVE); } } /** * * From server to client in response to command, if no error and no result set. * * <pre> * Bytes Name * ----- ---- * 1 field_count, always = 0 * 1-9 (Length Coded Binary) affected_rows * 1-9 (Length Coded Binary) insert_id * 2 server_status * 2 warning_count * n (until end of packet) message fix:(Length Coded String) * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#OK_Packet * </pre> * * @param buffer */ public void writeOKBody(ByteBuf buffer, long affectedRows, long insertId, String message, Session session) { // filed count buffer.writeByte(0x00); // affected rows BufferUtils.writeLength(buffer, affectedRows); // inserId BufferUtils.writeLength(buffer, insertId); // server status int status = 0; if (session.isAutoCommit()) { status |= SERVER_STATUS_AUTOCOMMIT; } Transaction trx = session.getTransaction(); if (trx != null) { if (trx.getTrxId() < 0) { status |= SERVER_STATUS_IN_TRANS; } } BufferUtils.writeUB2(buffer, status); // warning count = 0 BufferUtils.writeUB2(buffer, 0); // message if (message != null) { BufferUtils.writeLenString(buffer, message, Charsets.UTF_8); } } /** * * From server to client in response to command, if error. * * <pre> * Bytes Name * ----- ---- * 1 field_count, always = 0xff * 2 errno * 1 (sqlstate marker), always '#' * 5 sqlstate (5 characters) * n message * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Error_Packet * </pre> * * @param buffer * @param errno * @param message */ public void writeErrorBody(ByteBuf buffer, int errno, ByteBuffer message) { // field count buffer.writeByte((byte) 0xff); // error number BufferUtils.writeUB2(buffer, errno); // sql state mark buffer.writeByte((byte) '#'); // sql state buffer.writeBytes("HY000".getBytes()); if (message != null) { buffer.writeBytes(message); } } /** * * From Server To Client, at the end of a series of Field Packets, and at the * end of a series of Data Packets.With prepared statements, EOF Packet can also * end parameter information, which we'll describe later. * * <pre> * Bytes Name * ----- ---- * 1 field_count, always = 0xfe * 2 warning_count * 2 Status Flags * * @see http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#EOF_Packet * </pre> * * * @param out * @param session */ public void writeEOFBody(ByteBuf out, Session session) { // field count out.writeByte((byte) 0xfe); // warning count = 0 BufferUtils.writeUB2(out, 0); // status = 2 int status = 0x20; if (session.isAutoCommit()) { status |= SERVER_STATUS_AUTOCOMMIT; } Transaction trx = session.getTransaction(); if (trx != null) { if (trx.getTrxId() < 0) { status |= SERVER_STATUS_IN_TRANS; } } BufferUtils.writeUB2(out, status); } /** * * Registers a slave at the master. Should be sent before requesting a binlog events with COM_BINLOG_DUMP. * * <pre> * Bytes Name * ----- ---- * 1 [15] COM_REGISTER_SLAVE * 4 server-id * 1 slaves hostname length * string[$len] slaves hostname * 1 slaves user len * string[$len] slaves user * 1 slaves password len * string[$len] slaves password * 2 slaves mysql-port * 4 replication rank * 4 master-id * * @see https://dev.mysql.com/doc/internals/en/com-register-slave.html * </pre> * */ public void writeRegisterSlave(ByteBuf buffer, int serverId) { // code for COM_REGISTER_SLAVE is 0x15 buffer.writeByte(0x15); BufferUtils.writeUB4(buffer, serverId); // usually empty BufferUtils.writeLenString(buffer, "", Charsets.UTF_8); // usually empty BufferUtils.writeLenString(buffer, "", Charsets.UTF_8); // usually empty BufferUtils.writeLenString(buffer, "", Charsets.UTF_8); // usually empty BufferUtils.writeUB2(buffer, 0); // replication rank to be ignored buffer.writeBytes(new byte[4]); // master id, usually 0 BufferUtils.writeUB4(buffer, 0); } /** * * Requests a binlog network stream from the master starting a given position. * * <pre> * Bytes Name * ----- ---- * 1 [12] COM_BINLOG_DUMP * 4 binlog-pos * 2 flags * 4 server-id * string[EOF] binlog-filename * * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump.html * </pre> * */ public void writeBinlogDump(ByteBuf buffer, long binlogPos, int serverId, String binlogName) { // code for COM_BINLOG_DUMP is 0x12 buffer.writeByte(0x12); BufferUtils.writeUB4(buffer, binlogPos); // flag, 0 means don't send EOF if no more bing log BufferUtils.writeUB2(buffer, 0); // server id of this slave BufferUtils.writeUB4(buffer, serverId); // binlog file name BufferUtils.writeString(buffer, binlogName); } /** * * Handshake responce for replication master. * * <pre> * Bytes Name * ----- ---- * 2 capability flags, CLIENT_PROTOCOL_41 never set * 3 max-packet size * string[NUL] username * string[NUL] auth-response * string[NUL] database * * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump.html * </pre> * */ public void writeHandshakeResponse(ByteBuf buf, int capability, String user, String password) { // capability flags BufferUtils.writeInt(buf, 42117); // max-packet size, 0? BufferUtils.writeLongInt(buf, 0); BufferUtils.writeString(buf, user); BufferUtils.writeString(buf, password); // default dbname BufferUtils.writeString(buf, ""); } public void setCodec(Charset cs, int csidx) { this.cs = cs; this.csidx = csidx; } }