用C++解析MYSQL客户端服务器通信协议

         MYSQL是一个开源数据库,它自定义了一套客户端和服务器之间的底层通信协议,默认使用TCP 3306端口通信。

        根据MYSQL客户端和服务器的通信协议规范定义,MYSQL通信协议的报文包括建立连接的服务端握手报文、客户端握手应答、客户端执行查询等操作命令报文以及服务端的相应的应答报文。协议文档官网地址如下:

官方在线文档https://dev.mysql.com/doc/internals/en/client-server-protocol.html        MYSQL报文结构如下图所示,其中的payload字段是每一种报文的载荷,每一种报文的载荷编码结构在官方在线文档中有详细说明。

        建立连接的握手请求报文包括HandshakeV10和HandsharkV9。命令报文由命令等字段组成,命令字段标识了命令报文类型,命令可选值如下表所示:

命令

常量名称

说明

0x00

COM_SLEEP

仅服务器内部使用

0x01

COM_QUIT

客户端关闭连接

0x02

COM_INIT_DB

修改连接的默认数据库

0x03

COM_QUERY

请求执行一个查询语句

0x04

COM_FIELD_LIST

获取表格的所有字段名

0x05

COM_CREATE_DB

创建数据库

0x06

COM_DROP_DB

删除数据库

0x07

COM_REFRESH

执行REFRESH或者FLUSH命令

0x08

COM_SHUTDOWN

关闭服务器

0x09

COM_STATICTICS

获取线程信息

0x0a

COM_PROCESS_INFO

获取线程信息

0x0b

COM_CONNECT

仅服务器内部使用

0x0c

COM_PROCESS_KILL

请求中断连接

0x0d

COM_DEBUG

导出调试信息到标准输出

0x0e

COM_PING

检查服务端是否存活

0x0f

COM_TIME

仅服务器内部使用

0x10

COM_DELAYED_INSERT

仅服务器内部使用

0x11

COM_CHANGE_USER

修改当前连接的用户

0x12

COM_BINGLOG_DUMP

导出BINLOG

0x13

COM_TABLE_DUMP

导出表

0x14

COM_CONNECT_OUT

仅服务器内部使用

0x15

COM_REGISTER_SLAVE

注册从节点到主节点

0x16

COM_STMT_PREPARE

创建预编译语句

0x17

COM_STMT_EXECUTE

执行预编译语句

0x18

COM_STMT_SEND_LONG_DATA

发送预编译列数据

0x19

COM_STMT_CLOSE

关闭预编译语句

0x1a

COM_STMT_RESET

重置预编译语句的数据

0x1b

COM_SET_OPTION

开启或关闭CLIENT_MULTI_STATEMENTS

0x1c

COM_STMT_FETCH

从结果集中取数据

0x1d

COM_DAEMON

仅服务器内部使用

0x1e

COM_BINLOG_DUMP_GTID

请求一个BINLOG网络流

0x1f

COM_RESET_CONNECTION

重置会话状态

0xef

Semi-Synchronous

半同步复制

0xfb

LOCAL_INFILE_Request

加载本地数据文件到服务器

        应答报文包括OK_Packet、ERR_Packet等通用的应答,以及特定命令的数据应答如HandshakeResponse41、HandshakeResponse320等,如下表所示:

类型

说明

OK_Packet

命令执行成功

ERR_Packet

出现异常

EOF_Packet

同OK_Packet,MySQL 5.7.5开始取消此报文

HandshakeResponse41

客户端握手报文应答(双方支持CLIENT_PROTOCOL_41)

HandshakeResponse320

客户端握手报文应答(不支持CLIENT_PROTOCOL_41)

ColmunDefinition41

查询等操作应答(双方支持CLIENT_PROTOCOL_41)

ColmunDefinition320

查询等操作应答(不支持CLIENT_PROTOCOL_41)

COM_STMT_PREPARE_OK

创建预编译语句应答

Semi-Synchronous ACK

半同步复制应答

        每种报文的报文结构,在官方的在线文档中都有详细的说明。

        下面是解析代码:

/**
 * @file      mysql.h
 * @brief     Mysql Protocol
 * @author    yangyiyin
 * @date      2021/12
 * @copyright 
 */

#ifndef _MYSQL_H_
#define _MYSQL_H_

#include <string>
#include <vector>
#include <map>
#include "dissector.h"

#define MYSQL_DEFULT_PORT                        3306

/* Command */
#define MYSQL_COM_SLEEP                          0x00
#define MYSQL_COM_QUIT                           0x01
#define MYSQL_COM_INIT_DB                        0x02
#define MYSQL_COM_QUERY                          0x03
#define MYSQL_COM_FIELD_LIST                     0x04
#define MYSQL_COM_CREATE_DB                      0x05
#define MYSQL_COM_DROP_DB                        0x06
#define MYSQL_COM_REFRESH                        0x07
#define MYSQL_COM_SHUTDOWN                       0x08
#define MYSQL_COM_STATICTICS                     0x09
#define MYSQL_COM_PROCESS_INFO                   0x0a
#define MYSQL_COM_CONNECT                        0x0b
#define MYSQL_COM_PROCESS_KILL                   0x0c
#define MYSQL_COM_DEBUG                          0x0d
#define MYSQL_COM_PING                           0x0e
#define MYSQL_COM_TIME                           0x0f
#define MYSQL_COM_DELAY_INSERT                   0x10
#define MYSQL_COM_CHANGE_USER                    0x11
#define MYSQL_COM_BINLOG_DUMP                    0x12
#define MYSQL_COM_TABLE_DUMP                     0x13
#define MYSQL_COM_CONNECT_OUT                    0x14
#define MYSQL_COM_REGISTER_SLAVE                 0x15
#define MYSQL_COM_STMT_PREPARE                   0x16
#define MYSQL_COM_STMT_EXECUTE                   0x17
#define MYSQL_COM_STMT_SEND_LONG_DATA            0x18
#define MYSQL_COM_STMT_CLOSE                     0x19
#define MYSQL_COM_STMT_RESET                     0x1a
#define MYSQL_COM_SET_OPTION                     0x1b
#define MYSQL_COM_STMT_FETCH                     0x1c
#define MYSQL_COM_DAEMON                         0x1d
#define MYSQL_COM_BINLOG_DUMP_GTID               0x1e
#define MYSQL_COM_RESET_CONNECTION               0x1f
#define MYSQL_COM_SEMI_SYNC                      0xef

#define MYSQL_OK_PACKET_HEADER_1                 0x00
#define MYSQL_EOF_PACKET_HEADER                  0xfe
#define MYSQL_TEXT_RESULT_RAW                    0xfb
#define MYSQL_ERR_PACKET_HEADER                  0xff
                                                 
/* Handshake Protocol Version */                 
#define MYSQL_HANDSHAKE_PROTOCOL_V10             10
#define MYSQL_HANDSHAKE_PROTOCOL_V9              9

/* Capability Flags, Low 2 bytes
https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags : */
#define CLIENT_LONG_PASSWORD                     0x0001
#define CLIENT_FOUND_ROWS                        0x0002
#define CLIENT_LONG_FLAG                         0x0004
#define CLIENT_CONNECT_WITH_DB                   0x0008
#define CLIENT_NO_SCHEMA                         0x0010
#define CLIENT_COMPRESS                          0x0020
#define CLIENT_ODBC                              0x0040
#define CLIENT_LOCAL_FILES                       0x0080
#define CLIENT_IGNORE_SPACE                      0x0100
#define CLIENT_PROTOCOL_41                       0x0200
#define CLIENT_INTERACTIVE                       0x0400
#define CLIENT_SSL                               0x0800
#define CLIENT_IGNORE_SIGPIPE                    0x1000
#define CLIENT_TRANSACTIONS                      0x2000
#define CLIENT_RESERVED                          0x4000
#define CLIENT_SECURE_CONNECTION                 0x8000

/* Capability Flags, High 2 bytes */
#define CLIENT_MULTI_STATEMENTS                  0x0001
#define CLIENT_MULTI_RESULTS                     0x0002
#define CLIENT_PS_MULTI_RESULTS                  0x0004
#define CLIENT_PLUGIN_AUTH                       0x0008
#define CLIENT_CONNECT_ATTRS                     0x0010
#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA    0x0020
#define CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS      0x0040
#define CLIENT_SESSION_TRACK                     0x0080
#define CLIENT_DEPRECATE_EOF                     0x0100

/* StatusFlags Flags 
https://dev.mysql.com/doc/internals/en/status-flags.html#packet-Protocol::StatusFlags : */
#define SERVER_STATUS_IN_TRANS                   0x0001
#define SERVER_STATUS_AUTOCOMMIT                 0x0002
#define SERVER_STATUS_MUITI_QUERY                0x0004
#define SERVER_MORE_RESULTS_EXISTS               0x0008
#define SERVER_STATUS_NO_GOOD_INDEX_USED         0x0010
#define SERVER_STATUS_NO_INDEX_USED              0x0020
#define SERVER_STATUS_CURSOR_EXISTS              0x0040
#define SERVER_STATUS_LAST_ROW_SENT              0x0080
#define SERVER_STATUS_DB_DROPPED                 0x0100
#define SERVER_STATUS_NO_BACKSLASH_ESCAPES       0x0200
#define SERVER_STATUS_METADATA_CHANGED           0x0400
#define SERVER_QUERY_WAS_SLOW                    0x0800
#define SERVER_PS_OUT_PARAMS                     0x1000
#define SERVER_STATUS_IN_TRANS_READONLY          0x2000
#define SERVER_SESSION_STATE_CHANGED             0x4000

/* OK_Packet: Session Track Type */
#define SESSION_TRACK_SYSTEM_VARIABLES           0x00
#define SESSION_TRACK_SCHEMA                     0x01
#define SESSION_TRACK_STATE_CHANGE               0x02
#define SESSION_TRACK_GTIDS                      0x03

/* Refresh Subcommand */
#define REFRESH_GRANT                            0x01
#define REFRESH_LOG                              0x02
#define REFRESH_TABLES                           0x04
#define REFRESH_HOSTS                            0x08
#define REFRESH_STATUS                           0x10
#define REFRESH_THREADS                          0x20
#define REFRESH_SLAVE                            0x40
#define REFRESH_MASTER                           0x80

/* Shutdown Type */
#define SHUTDOWN_DEFAULT                         0x00
#define SHUTDOWN_WAIT_CONNECTIONS                0x01
#define SHUTDOWN_WAIT_TRANSACTIONS               0x02
#define SHUTDOWN_WAIT_UPDATES                    0x08
#define SHUTDOWN_WAIT_ALL_BUFFERS                0x10
#define SHUTDOWN_WAIT_CRITICAL_BUFFERS           0x11
#define KILL_QUERY                               0xfe
#define KILL_CONNECTION                          0xff

/* Character Set */
#define DEC8_SWEDISH_CI                          3
#define CP850_GENERAL_CI                         4
#define KOI8R_GENERAL_CI                         7
#define LATIN1_SWEDISH_CI                        8
#define LATIN2_GENERAL_CI                        9
#define SWE7_SWEDISH_CI                          10
#define ASCII_GENERAL_CI                         11
#define UJIS_JAPANESE_CI                         12
#define SJIS_JAPANESE_CI                         13
#define CP1251_BULGARIAN_CI                      14
#define LATIN1_DANISH_CI                         15
#define HEBREW_GENERAL_CI                        16
#define LATIN7_ESTONIAN_CS                       20
#define LATIN2_HUNGARIAN_CI                      21
#define KOI8U_GENERAL_CI                         22
#define CP1251_UKRAINIAN_CI                      23
#define GREEK_GENERAL_CI                         25
#define CP1250_GENERAL_CI                        26
#define LATIN2_CROATIAN_CI                       27
#define CP1257_LITHUANIAN_CI                     29
#define LATIN5_TURKISH_CI                        30
#define LATIN1_GERMAN2_CI                        31
#define ARMSCII8_GENERAL_CI                      32
#define UTF8_GENERAL_CI                          33
#define CP866_GENERAL_CI                         36
#define KEYBCS2_GENERAL_CI                       37
#define MACCE_GENERAL_CI                         38
#define MACROMAN_GENERAL_CI                      39
#define CP852_GENERAL_CI                         40
#define LATIN7_GENERAL_CI                        41
#define LATIN7_GENERAL_CS                        42
#define MACCE_BIN                                43
#define CP1250_CROATIAN_CI                       44
#define UTF8MB4_GENERAL_CI                       45
#define UTF8MB4_BIN                              46
#define LATIN1_BIN                               47
#define LATIN1_GENERAL_CI                        48
#define LATIN1_GENERAL_CS                        49
#define CP1251_BIN                               50
#define CP1251_GENERAL_CI                        51
#define CP1251_GENERAL_CS                        52
#define MACROMAN_BIN                             53
#define CP1256_GENERAL_CI                        57
#define CP1257_BIN                               58
#define CP1257_GENERAL_CI                        59
#define BINARY                                   63
#define ARMSCII8_BIN                             64
#define ASCII_BIN                                65
#define CP1250_BIN                               66
#define CP1256_BIN                               67
#define CP866_BIN                                68
#define DEC8_BIN                                 69
#define GREEK_BIN                                70
#define HEBREW_BIN                               71
#define HP8_BIN                                  72
#define KEYBCS2_BIN                              73
#define KOI8R_BIN                                74
#define KOI8U_BIN                                75
#define LATIN2_BIN                               77
#define LATIN5_BIN                               78
#define LATIN7_BIN                               79
#define CP850_BIN                                80
#define CP852_BIN                                81
#define SWE7_BIN                                 82
#define UTF8_BIN                                 83
#define GEOSTD8_GENERAL_CI                       92
#define GEOSTD8_BIN                              93
#define LATIN1_SPANISH_CI                        94
#define CP1250_POLISH_CI                         99
#define UTF8_UNICODE_CI                          192
#define UTF8_ICELANDIC_CI                        193
#define UTF8_LATVIAN_CI                          194
#define UTF8_ROMANIAN_CI                         195
#define UTF8_SLOVENIAN_CI                        196
#define UTF8_POLISH_CI                           197
#define UTF8_ESTONIAN_CI                         198
#define UTF8_SPANISH_CI                          199
#define UTF8_SWEDISH_CI                          200
#define UTF8_TURKISH_CI                          201
#define UTF8_CZECH_CI                            202
#define UTF8_DANISH_CI                           203
#define UTF8_LITHUANIAN_CI                       204
#define UTF8_SLOVAK_CI                           205
#define UTF8_SPANISH2_CI                         206
#define UTF8_ROMAN_CI                            207
#define UTF8_PERSIAN_CI                          208
#define UTF8_ESPERANTO_CI                        209
#define UTF8_HUNGARIAN_CI                        210
#define UTF8_SINHALA_CI                          211
#define UTF8_GERMAN2_CI                          212
#define UTF8_CROATIAN_CI                         213
#define UTF8_UNICODE_520_CI                      214
#define UTF8_VIETNAMESE_CI                       215
#define UTF8_GENERAL_MYSQL500_CI                 223
#define UTF8MB4_UNICODE_CI                       224
#define UTF8MB4_ICELANDIC_CI                     225
#define UTF8MB4_LATVIAN_CI                       226
#define UTF8MB4_ROMANIAN_CI                      227
#define UTF8MB4_SLOVENIAN_CI                     228
#define UTF8MB4_POLISH_CI                        229
#define UTF8MB4_ESTONIAN_CI                      230
#define UTF8MB4_SPANISH_CI                       231
#define UTF8MB4_SWEDISH_CI                       232
#define UTF8MB4_TURKISH_CI                       233
#define UTF8MB4_CZECH_CI                         234
#define UTF8MB4_DANISH_CI                        235
#define UTF8MB4_LITHUANIAN_CI                    236
#define UTF8MB4_SLOVAK_CI                        237
#define UTF8MB4_SPANISH2_CI                      238
#define UTF8MB4_ROMAN_CI                         239
#define UTF8MB4_PERSIAN_CI                       240
#define UTF8MB4_ESPERANTO_CI                     241
#define UTF8MB4_HUNGARIAN_CI                     242
#define UTF8MB4_SINHALA_CI                       243
#define UTF8MB4_GERMAN2_CI                       244
#define UTF8MB4_CROATIAN_CI                      245
#define UTF8MB4_UNICODE_520_CI                   246
#define UTF8MB4_VIETNAMESE_CI                    247

/* Field types */
#define MYSQL_TYPE_DECIMAL                       0x00
#define MYSQL_TYPE_TINY                          0x01
#define MYSQL_TYPE_SHORT                         0x02
#define MYSQL_TYPE_LONG                          0x03
#define MYSQL_TYPE_FLOAT                         0x04
#define MYSQL_TYPE_DOUBLE                        0x05
#define MYSQL_TYPE_NULL                          0x06
#define MYSQL_TYPE_TIMESTAMP                     0x07
#define MYSQL_TYPE_LONGLONG                      0x08
#define MYSQL_TYPE_INT24                         0x09
#define MYSQL_TYPE_DATE                          0x0a
#define MYSQL_TYPE_TIME                          0x0b
#define MYSQL_TYPE_DATETIME                      0x0c
#define MYSQL_TYPE_YEAR                          0x0d
#define MYSQL_TYPE_NEWDATE                       0x0e
#define MYSQL_TYPE_VARCHAR                       0x0f
#define MYSQL_TYPE_BIT                           0x10
#define MYSQL_TYPE_TIMESTAMP2                    0x11
#define MYSQL_TYPE_DATETIME2                     0x12
#define MYSQL_TYPE_TIME2                         0x13
#define MYSQL_TYPE_NEWDECIMAL                    0xf6
#define MYSQL_TYPE_ENUM                          0xf7
#define MYSQL_TYPE_SET                           0xf8
#define MYSQL_TYPE_TINY_BLOB                     0xf9
#define MYSQL_TYPE_MEDIUM_BLOB                   0xfa
#define MYSQL_TYPE_LONG_BLOB                     0xfb
#define MYSQL_TYPE_BLOB                          0xfc
#define MYSQL_TYPE_VAR_STRING                    0xfd
#define MYSQL_TYPE_STRING                        0xfe
#define MYSQL_TYPE_GEOMETRY                      0xff

#define MYSQL_OPTION_MULTI_STATEMENTS_ON         0
#define MYSQL_OPTION_MULTI_STATEMENTS_OFF        1

#define BINLOG_DUMP_NON_BLOCK                    0x01
#define BINLOG_THROUGH_POSITION                  0x02
#define BINLOG_THROUGH_GTID                      0x04

struct LengthEncodedInteger
{
    LengthEncodedInteger()
    {
        prefix = 0;
        length = 0;
        value = 0;
    }

    uint8_t prefix;
    uint8_t length;
    uint64_t value;
};

struct LengthEncodedString
{
    uint64_t Length()
    {
        return length.length + length.value;
    }

    LengthEncodedInteger length;
    String value;
};

using EOFString = String;
using NULString = String;
using FIXString = String;

#define MYSQL_PACKET_HEADER_LENGTH               4

/* MySQL Packet Header */
struct MySQLPacketHeaderPDU
{
    uint8_t payload_len[3];
    uint8_t sequence_id;
};

struct MySQLPacketHeader
{
    MySQLPacketHeader()
    {
        payload_len = 0;
        sequence_id = 0;
    }

    MySQLPacketHeaderPDU* pdu; 

    uint32_t payload_len;
    uint8_t sequence_id;
};

#define MYSQL_HANDSHAKE_REQUEST_V10_LENGTH_MIN   17
#define MYSQL_HANDSHAKE_APDP_1_LENGTH            8
#define MYSQL_HANDSHAKE_RESERVED_LENGTH          10
#define MYSQL_HANDSHAKE_APDP_2_LENGTH_MIN        13

/* https://dev.mysql.com/doc/internals/en/connectio-phase-packets.html : HandshakeV10 */
struct HandshakeRequestV10
{
    bool CapabilityFlagsBit(uint32_t bit)
    {
        static uint32_t flags = (capablity_flags_upper << 16) + capablity_flags_lower;
        return flags & bit;
    }

    bool StatusFlagBit(uint16_t bit)
    {
        return status_flag & bit;
    }

    uint8_t protocol_version;
    NULString server_version;
    uint32_t connection_id;
    //char auth_plugin_data_part1[MYSQL_HANDSHAKE_APDP_1_LENGTH];
    FIXString auth_plugin_data_part1; // MYSQL_HANDSHAKE_APDP_1_LENGTH
    uint8_t filler;
    uint16_t capablity_flags_lower;
    uint8_t character_set;
    uint16_t status_flag;
    uint16_t capablity_flags_upper;
    uint8_t auth_plugin_data_len;    
    FIXString reserved;  // MYSQL_HANDSHAKE_RESERVED_LENGTH
    FIXString auth_plugin_data_part2;
    EOFString auth_plugin_name;
};

#define MYSQL_HANDSHAKE_RESPONSE_41_LENGTH_MIN      32
#define MYSQL_HANDSHAKE_RESPONSE_320_LENGTH_MIN     33
#define MYSQL_HANDSHAKE_RESPONSE_41_RESERVED_LENGTH 23

struct MySQLConnectAttribute
{
    LengthEncodedString key;
    LengthEncodedString value;
};

/* https://dev.mysql.com/doc/internals/en/connectio-phase-packets.html : HandshakeResponse41 */
struct HandshakeResponse41
{
    bool CapabilityFlagsBit(uint32_t bit)
    {
        return capability_flags & bit;
    }

    MySQLPacketHeader hdr;

    uint32_t capability_flags;
    uint32_t max_packet_size;
    uint8_t character_set;
    FIXString reserved;   // MYSQL_HANDSHAKE_RESPONSE_41_RESERVED_LENGTH
    NULString username;
    LengthEncodedInteger auth_response_len;
    FIXString auth_response;
    NULString database;
    NULString auth_plugin_name;
    LengthEncodedInteger attr_len;
    std::vector<MySQLConnectAttribute> attrs;
};

struct CommandRequest
{
    const char* Command()
    {
        switch (command)
        {
        case MYSQL_COM_SLEEP: return "Sleep";
        case MYSQL_COM_QUIT: return "Quit";
        case MYSQL_COM_INIT_DB: return "Use Database";
        case MYSQL_COM_QUERY: return "Query";
        case MYSQL_COM_FIELD_LIST: return "Show Fields";
        case MYSQL_COM_CREATE_DB: return "Create Database";
        case MYSQL_COM_DROP_DB: return "Drop Database";
        case MYSQL_COM_REFRESH: return "Refresh";
        case MYSQL_COM_SHUTDOWN: return "Shutdown";
        case MYSQL_COM_STATICTICS: return "Statictics";
        case MYSQL_COM_PROCESS_INFO: return "Process Information";
        case MYSQL_COM_CONNECT: return "Connect";
        case MYSQL_COM_PROCESS_KILL: return "Process Kill";
        case MYSQL_COM_DEBUG: return "Debug";
        case MYSQL_COM_PING: return "Ping";
        case MYSQL_COM_TIME: return "Time";
        case MYSQL_COM_DELAY_INSERT: return "Delay Insert";
        case MYSQL_COM_CHANGE_USER: return "Change User";
        case MYSQL_COM_BINLOG_DUMP: return "Send Binlog";
        case MYSQL_COM_TABLE_DUMP: return "Table Dump";
        case MYSQL_COM_CONNECT_OUT: return "Connect Out";
        case MYSQL_COM_REGISTER_SLAVE: return "Register Slave";
        case MYSQL_COM_STMT_PREPARE: return "Prepare Statement";
        case MYSQL_COM_STMT_EXECUTE: return "Execute Statement";
        case MYSQL_COM_STMT_SEND_LONG_DATA: return "Send Statement Long Data";
        case MYSQL_COM_STMT_CLOSE: return "Close Statement";
        case MYSQL_COM_STMT_RESET: return "Reset Statement";
        case MYSQL_COM_SET_OPTION: return "Set Option";
        case MYSQL_COM_STMT_FETCH: return "Fetch Statement";
        case MYSQL_COM_DAEMON: return "STMT Daemon";
        case MYSQL_COM_BINLOG_DUMP_GTID: return "Binlog Dump GTID";
        case MYSQL_COM_RESET_CONNECTION: return "Reset Connection";
        case MYSQL_COM_SEMI_SYNC: return "SEMI Synchronous";
        default: return "Unkonwn command";
        }
    }

    /* COM_INIT_DB */
    struct InitDB
    {
        EOFString schema_name;
    };

    /* COM_QUERY */
    struct Query
    {
        EOFString statement;
    };

    /* COM_FIELD_LIST */
    struct FieldList
    {
        NULString table;
        EOFString field_wildcard;
    };

    /* COM_CREATE_DB */
    struct CreateDB
    {
        EOFString schema_name;
    };

    /* COM_DROP_DB */
    struct DropDB
    {
        EOFString schema_name;
    };

    /* COM_REFRESH */
    struct Refresh
    {
        const char* SubCommand()
        {
            switch (sub_command)
            {
            case REFRESH_GRANT: return "Grant";
            case REFRESH_LOG: return "Log";
            case REFRESH_TABLES: return "Tables";
            case REFRESH_HOSTS: return "Hosts";
            case REFRESH_STATUS: return "Status";
            case REFRESH_THREADS: return "Threads";
            case REFRESH_SLAVE: return "Slave";
            case REFRESH_MASTER: return "Master";
            default: return "Unknown";
            }
        }

        uint8_t sub_command;
    };

    /* COM_SHUTDOWN */
    struct Shutdown
    {
        const char* Type()
        {
            switch (shutdown_type)
            {
            case SHUTDOWN_DEFAULT: return "Default";
            case SHUTDOWN_WAIT_CONNECTIONS: return "Wait Connections";
            case SHUTDOWN_WAIT_TRANSACTIONS: return "Wait Transactions";
            case SHUTDOWN_WAIT_UPDATES: return "Wait Updates";
            case SHUTDOWN_WAIT_ALL_BUFFERS: return "Wait All Buffers";
            case SHUTDOWN_WAIT_CRITICAL_BUFFERS: return "Wait Critical Buffers";
            case KILL_QUERY: return "Kill Query";
            case KILL_CONNECTION: return "Kill Conection";
            default: return "Unknown";
            }
        }

        uint8_t shutdown_type;
    };

    /* COM_PROCESS_KILL */
    struct ProcessKill
    {
        uint32_t connection_id;
    };

    /* COM_CHANGE_USER */
    struct ChangeUser
    {
        NULString user;
        uint8_t auth_response_len;
        FIXString auth_response;
        NULString schema_name;
        uint16_t character_set;
        NULString auth_plugin_name;
        LengthEncodedInteger attr_len;
        std::vector<MySQLConnectAttribute> attrs;
    };

    /* COM_BINLOG_DUMP */
    struct BinLogDump
    {
        uint32_t binlog_pos;
        uint16_t flags;
        uint32_t server_id;
        EOFString binlog_file_name;
    };

    /* COM_BINLOG_DUMP_GTID */
    struct BinLogDumpGTID
    {
        uint16_t flags;
        uint32_t server_id;
        uint32_t binlog_filename_len;
        FIXString binlog_filename;
        uint64_t binlog_pos;
        uint32_t data_size;
        FIXString data;
    };

    /* COM_TABLE_DUMP */
    struct TableDump
    {
        uint8_t database_len;
        FIXString database_name;
        uint8_t table_len;
        FIXString table_name;
    };

    /* COM_REGISTER_SLAVE */
    struct RegisterSlave
    {
        uint32_t server_id;
        uint8_t hostname_len;
        FIXString hostname;
        uint8_t user_len;
        FIXString user;
        uint8_t password_len;
        FIXString password;
        uint16_t port;
        uint32_t replication_rank;
        uint32_t master_id;
    };

    /* MYSQL_COM_STMT_PREPARE */
    struct STMTPrepare
    {
        EOFString query;
    };

    /* COM_STMT_SEND_LONG_DATA */
    struct STMTSendLongData
    {
        uint32_t statement_id;
        uint16_t param_id;
        EOFString data;
    };

    /* COM_STMT_CLOSE */
    struct STMTClose
    {
        uint32_t statement_id;
    };

    /* COM_STMT_RESET */
    struct STMTReset
    {
        uint32_t statement_id;
    };

    /* COM_STMT_FETCH */
    struct STMTFetch
    {
        uint32_t statement_id;
        uint32_t num_rows;
    };

    /* COM_SET_OPTION */
    struct SetOption
    {
        const char* OptionOperation()
        {
            switch (option_operation)
            {
            case MYSQL_OPTION_MULTI_STATEMENTS_ON: return "MYSQL_OPTION_MULTI_STATEMENTS_ON";
            case MYSQL_OPTION_MULTI_STATEMENTS_OFF: return "MYSQL_OPTION_MULTI_STATEMENTS_OFF";
            default: return "?";
            }
        }

        uint16_t option_operation;
    };

    /* COM_SEMI_SYNC */
    struct SEMISync
    {
        uint8_t flag;
    };

    uint8_t command;

    InitDB initDB;
    Query query;
    FieldList fieldList;
    CreateDB createDB;
    DropDB dropDB;
    Refresh refresh;
    Shutdown shutdown;
    ProcessKill processKill;
    ChangeUser changeUser;
    BinLogDump binLogDump;
    BinLogDumpGTID binLogDumpGTID;
    TableDump tableDump;
    RegisterSlave registerSlave;
    STMTPrepare stmtPrepare;
    STMTSendLongData stmtSendData;
    STMTClose stmtClose;
    STMTReset stmtReset;
    STMTFetch stmtFetch;
    SetOption setOption;
    SEMISync semiSync;
};

#define MYSQL_OK_PACKET_LENGTH_MIN       7

/* OK_Packet */
struct OKResponse
{
    struct SessionStateInfoData
    {
        /* MYSQL_SESSION_TRACK_SYSTEM_VARIABLES */
        struct SessionTrackSystemVariable
        {
            LengthEncodedString name;
            LengthEncodedString value;
        };

        /* MYSQL_SESSION_TRACK_SCHEMA */
        struct SessionTrackSchema
        {
            LengthEncodedString name;
        };

        /* MYSQL_SESSION_TRACK_STATE_CHANGE */
        struct SessionTrackStateChange
        {
            LengthEncodedString is_tracked;
        };

        /* MYSQL_SESSION_TRACK_GTIDS */
        struct SessionTrackGTIDS
        {
            LengthEncodedString grids;
        };

        SessionTrackSystemVariable track_sys_var;
        SessionTrackSchema track_schema;
        SessionTrackStateChange track_state_change;
        SessionTrackGTIDS track_gtids;
    };

    struct SessionStateInfo
    {
        const char* Type()
        {
            switch (type)
            {
            case SESSION_TRACK_SYSTEM_VARIABLES: return "SESSION_TRACK_SYSTEM_VARIABLES";
            case SESSION_TRACK_SCHEMA: return "SESSION_TRACK_SCHEMA";
            case SESSION_TRACK_STATE_CHANGE: return "SESSION_TRACK_STATE_CHANGE";
            case SESSION_TRACK_GTIDS: return "SESSION_TRACK_GTIDS";
            default: return "?";
            }
        }
        uint8_t type;
        
        LengthEncodedString data_str; // data_str的字符串值就是data的字节流数据
        SessionStateInfoData data;    // data_str的字符串值解码后的结构
    };

    struct SesstionStateChanges
    {   
        LengthEncodedString infos_str; // infos_str的字符串值就是info的字节流数据
        SessionStateInfo infos;        // infos_str的字符串值解码后的结构
    };

    bool StatusFlagBit(uint16_t bit)
    {
        return status_flag & bit;
    }

    uint8_t header;
    LengthEncodedInteger affected_rows;
    LengthEncodedInteger last_insert_id;
    uint16_t status_flag;
    uint16_t warnings;
    LengthEncodedString status_info;
    SesstionStateChanges state_changes;
};

#define MYSQL_ERROR_PACKET_LENGTH_MIN     4
#define MYSQL_ERROR_PACKET_SQL_STATE_LEN  5

/* ERR_Packet */
struct ERRResponse
{
    uint8_t header;
    uint16_t error_code;
    uint8_t sql_state_marker;
    //char sql_state[MYSQL_ERROR_PACKET_SQL_STATE_LEN];
    FIXString sql_state;
    EOFString error_msg;
};

/* EOF_Packet */
struct EOFResponse
{
    uint8_t header;
    uint16_t warnings;
    uint16_t status_flags;
};

/* ColumnDefinition41 */
struct ColumnDefinition41
{
    const char* Type()
    {
        switch (type)
        {
        case MYSQL_TYPE_DECIMAL: return "MYSQL_TYPE_DECIMAL";
        case MYSQL_TYPE_TINY: return "MYSQL_TYPE_TINY";
        case MYSQL_TYPE_SHORT: return "MYSQL_TYPE_SHORT";
        case MYSQL_TYPE_LONG: return "MYSQL_TYPE_LONG";
        case MYSQL_TYPE_FLOAT: return "MYSQL_TYPE_FLOAT";
        case MYSQL_TYPE_DOUBLE: return "MYSQL_TYPE_DOUBLE";
        case MYSQL_TYPE_NULL: return "MYSQL_TYPE_NULL";
        case MYSQL_TYPE_TIMESTAMP: return "MYSQL_TYPE_TIMESTAMP";
        case MYSQL_TYPE_LONGLONG: return "MYSQL_TYPE_LONGLONG";
        case MYSQL_TYPE_INT24: return "MYSQL_TYPE_INT24";
        case MYSQL_TYPE_DATE: return "MYSQL_TYPE_DATE";
        case MYSQL_TYPE_TIME: return "MYSQL_TYPE_TIME";
        case MYSQL_TYPE_DATETIME: return "MYSQL_TYPE_DATETIME";
        case MYSQL_TYPE_YEAR: return "MYSQL_TYPE_YEAR";
        case MYSQL_TYPE_NEWDATE: return "MYSQL_TYPE_NEWDATE";
        case MYSQL_TYPE_VARCHAR: return "MYSQL_TYPE_VARCHAR";
        case MYSQL_TYPE_BIT: return "MYSQL_TYPE_BIT";
        case MYSQL_TYPE_TIMESTAMP2: return "MYSQL_TYPE_TIMESTAMP2";
        case MYSQL_TYPE_DATETIME2: return "MYSQL_TYPE_DATETIME2";
        case MYSQL_TYPE_TIME2: return "MYSQL_TYPE_TIME2";
        case MYSQL_TYPE_NEWDECIMAL: return "MYSQL_TYPE_NEWDECIMAL";
        case MYSQL_TYPE_ENUM: return "MYSQL_TYPE_ENUM";
        case MYSQL_TYPE_SET: return "MYSQL_TYPE_SET";
        case MYSQL_TYPE_TINY_BLOB: return "MYSQL_TYPE_TINY_BLOB";
        case MYSQL_TYPE_MEDIUM_BLOB: return "MYSQL_TYPE_MEDIUM_BLOB";
        case MYSQL_TYPE_LONG_BLOB: return "MYSQL_TYPE_LONG_BLOB";
        case MYSQL_TYPE_BLOB: return "MYSQL_TYPE_BLOB";
        case MYSQL_TYPE_VAR_STRING: return "MYSQL_TYPE_VAR_STRING";
        case MYSQL_TYPE_STRING: return "MYSQL_TYPE_STRING";
        case MYSQL_TYPE_GEOMETRY: return "MYSQL_TYPE_GEOMETRY";
        default: return "?";
        }
    }

    ColumnDefinition41()
    {
        character_set = 0;
        column_len = 0;
        type = 0;
        flags = 0;
        decimals = 0;
        filler = 0;
    }

    LengthEncodedString catalog;
    LengthEncodedString schema;
    LengthEncodedString table;
    LengthEncodedString org_table;
    LengthEncodedString name;
    LengthEncodedString org_name;
    LengthEncodedInteger fixed_fields_len;
    uint16_t character_set;
    uint32_t column_len;
    uint8_t type;
    uint16_t flags;
    uint8_t decimals;
    uint16_t filler;
    LengthEncodedInteger default_values_len;
    FIXString default_values;
};

struct TextResultRow
{
    bool null;
    std::vector<LengthEncodedString> texts;
};

struct ColumnCount
{
    LengthEncodedInteger column_count;
};

struct MySQLDecodedObject
{
    const char* CharacterSet(uint8_t id)
    {
        switch (id)
        {
        case DEC8_SWEDISH_CI: return "dec8 COLLATE dec8_swedish_ci";
        case CP850_GENERAL_CI: return "cp850 COLLATE cp850_general_ci";
        case KOI8R_GENERAL_CI: return "koi8r COLLATE koi8r_general_ci";
        case LATIN1_SWEDISH_CI: return "latin1 COLLATE latin1_swedish_ci";
        case LATIN2_GENERAL_CI: return "latin2 COLLATE latin2_general_ci";
        case SWE7_SWEDISH_CI: return "swe7 COLLATE swe7_swedish_ci";
        case ASCII_GENERAL_CI: return "ascii COLLATE ascii_general_ci";
        case UJIS_JAPANESE_CI: return "ujis COLLATE ujis_japanese_ci";
        case SJIS_JAPANESE_CI: return "sjis COLLATE sjis_japanese_ci";
        case CP1251_BULGARIAN_CI: return "cp1251 COLLATE cp1251_bulgarian_ci";
        case LATIN1_DANISH_CI: return "latin1 COLLATE latin1_danish_ci";
        case HEBREW_GENERAL_CI: return "hebrew COLLATE hebrew_general_ci";
        case LATIN7_ESTONIAN_CS: return "latin7 COLLATE latin7_estonian_cs";
        case LATIN2_HUNGARIAN_CI: return "latin2 COLLATE latin2_hungarian_ci";
        case KOI8U_GENERAL_CI: return "koi8u COLLATE koi8u_general_ci";
        case CP1251_UKRAINIAN_CI: return "cp1251 COLLATE cp1251_ukrainian_ci";
        case GREEK_GENERAL_CI: return "greek COLLATE greek_general_ci";
        case CP1250_GENERAL_CI: return "cp1250 COLLATE cp1250_general_ci";
        case LATIN2_CROATIAN_CI: return "latin2 COLLATE latin2_croatian_ci";
        case CP1257_LITHUANIAN_CI: return "cp1257 COLLATE cp1257_lithuanian_ci";
        case LATIN5_TURKISH_CI: return "latin5 COLLATE latin5_turkish_ci";
        case LATIN1_GERMAN2_CI: return "latin1 COLLATE latin1_german2_ci";
        case ARMSCII8_GENERAL_CI: return "armscii8 COLLATE armscii8_general_ci";
        case UTF8_GENERAL_CI: return "utf8 COLLATE utf8_general_ci";
        case CP866_GENERAL_CI: return "cp866 COLLATE cp866_general_ci";
        case KEYBCS2_GENERAL_CI: return "keybcs2 COLLATE keybcs2_general_ci";
        case MACCE_GENERAL_CI: return "macce COLLATE macce_general_ci";
        case MACROMAN_GENERAL_CI: return "macroman COLLATE macroman_general_ci";
        case CP852_GENERAL_CI: return "cp852 COLLATE cp852_general_ci";
        case LATIN7_GENERAL_CI: return "latin7 COLLATE latin7_general_ci";
        case LATIN7_GENERAL_CS: return "latin7 COLLATE latin7_general_cs";
        case MACCE_BIN: return "macce COLLATE macce_bin";
        case CP1250_CROATIAN_CI: return "cp1250 COLLATE cp1250_croatian_ci";
        case UTF8MB4_GENERAL_CI: return "utf8mb4 COLLATE utf8mb4_general_ci";
        case UTF8MB4_BIN: return "utf8mb4 COLLATE utf8mb4_bin";
        case LATIN1_BIN: return "latin1 COLLATE latin1_bin";
        case LATIN1_GENERAL_CI: return "latin1 COLLATE latin1_general_ci";
        case LATIN1_GENERAL_CS: return "latin1 COLLATE latin1_general_cs";
        case CP1251_BIN: return "cp1251 COLLATE cp1251_bin";
        case CP1251_GENERAL_CI: return "cp1251 COLLATE cp1251_general_ci";
        case CP1251_GENERAL_CS: return "cp1251 COLLATE cp1251_general_cs";
        case MACROMAN_BIN: return "macroman COLLATE macroman_bin";
        case CP1256_GENERAL_CI: return "cp1256 COLLATE cp1256_general_ci";
        case CP1257_BIN: return "cp1257 COLLATE cp1257_bin";
        case CP1257_GENERAL_CI: return "cp1257 COLLATE cp1257_general_ci";
        case BINARY: return "binary COLLATE binary";
        case ARMSCII8_BIN: return "armscii8 COLLATE armscii8_bin";
        case ASCII_BIN: return "ascii COLLATE ascii_bin";
        case CP1250_BIN: return "cp1250 COLLATE cp1250_bin";
        case CP1256_BIN: return "cp1256 COLLATE cp1256_bin";
        case CP866_BIN: return "cp866 COLLATE cp866_bin";
        case DEC8_BIN: return "dec8 COLLATE dec8_bin";
        case GREEK_BIN: return "greek COLLATE greek_bin";
        case HEBREW_BIN: return "hebrew COLLATE hebrew_bin";
        case HP8_BIN: return "hp8 COLLATE hp8_bin";
        case KEYBCS2_BIN: return "keybcs2 COLLATE keybcs2_bin";
        case KOI8R_BIN: return "koi8r COLLATE koi8r_bin";
        case KOI8U_BIN: return "koi8u COLLATE koi8u_bin";
        case LATIN2_BIN: return "latin2 COLLATE latin2_bin";
        case LATIN5_BIN: return "latin5 COLLATE latin5_bin";
        case LATIN7_BIN: return "latin7 COLLATE latin7_bin";
        case CP850_BIN: return "cp850 COLLATE cp850_bin";
        case CP852_BIN: return "cp852 COLLATE cp852_bin";
        case SWE7_BIN: return "swe7 COLLATE swe7_bin";
        case UTF8_BIN: return "utf8 COLLATE utf8_bin";
        case GEOSTD8_GENERAL_CI: return "geostd8 COLLATE geostd8_general_ci";
        case GEOSTD8_BIN: return "geostd8 COLLATE geostd8_bin";
        case LATIN1_SPANISH_CI: return "latin1 COLLATE latin1_spanish_ci";
        case CP1250_POLISH_CI: return "cp1250 COLLATE cp1250_polish_ci";
        case UTF8_UNICODE_CI: return "utf8 COLLATE utf8_unicode_ci";
        case UTF8_ICELANDIC_CI: return "utf8 COLLATE utf8_icelandic_ci";
        case UTF8_LATVIAN_CI: return "utf8 COLLATE utf8_latvian_ci";
        case UTF8_ROMANIAN_CI: return "utf8 COLLATE utf8_romanian_ci";
        case UTF8_SLOVENIAN_CI: return "utf8 COLLATE utf8_slovenian_ci";
        case UTF8_POLISH_CI: return "utf8 COLLATE utf8_polish_ci";
        case UTF8_ESTONIAN_CI: return "utf8 COLLATE utf8_estonian_ci";
        case UTF8_SPANISH_CI: return "utf8 COLLATE utf8_spanish_ci";
        case UTF8_SWEDISH_CI: return "utf8 COLLATE utf8_swedish_ci";
        case UTF8_TURKISH_CI: return "utf8 COLLATE utf8_turkish_ci";
        case UTF8_CZECH_CI: return "utf8 COLLATE utf8_czech_ci";
        case UTF8_DANISH_CI: return "utf8 COLLATE utf8_danish_ci";
        case UTF8_LITHUANIAN_CI: return "utf8 COLLATE utf8_lithuanian_ci";
        case UTF8_SLOVAK_CI: return "utf8 COLLATE utf8_slovak_ci";
        case UTF8_SPANISH2_CI: return "utf8 COLLATE utf8_spanish2_ci";
        case UTF8_ROMAN_CI: return "utf8 COLLATE utf8_roman_ci";
        case UTF8_PERSIAN_CI: return "utf8 COLLATE utf8_persian_ci";
        case UTF8_ESPERANTO_CI: return "utf8 COLLATE utf8_esperanto_ci";
        case UTF8_HUNGARIAN_CI: return "utf8 COLLATE utf8_hungarian_ci";
        case UTF8_SINHALA_CI: return "utf8 COLLATE utf8_sinhala_ci";
        case UTF8_GERMAN2_CI: return "utf8 COLLATE utf8_german2_ci";
        case UTF8_CROATIAN_CI: return "utf8 COLLATE utf8_croatian_ci";
        case UTF8_UNICODE_520_CI: return "utf8 COLLATE utf8_unicode_520_ci";
        case UTF8_VIETNAMESE_CI: return "utf8 COLLATE utf8_vietnamese_ci";
        case UTF8_GENERAL_MYSQL500_CI: return "utf8 COLLATE utf8_general_mysql500_ci";
        case UTF8MB4_UNICODE_CI: return "utf8mb4 COLLATE utf8mb4_unicode_ci";
        case UTF8MB4_ICELANDIC_CI: return "utf8mb4 COLLATE utf8mb4_icelandic_ci";
        case UTF8MB4_LATVIAN_CI: return "utf8mb4 COLLATE utf8mb4_latvian_ci";
        case UTF8MB4_ROMANIAN_CI: return "utf8mb4 COLLATE utf8mb4_romanian_ci";
        case UTF8MB4_SLOVENIAN_CI: return "utf8mb4 COLLATE utf8mb4_slovenian_ci";
        case UTF8MB4_POLISH_CI: return "utf8mb4 COLLATE utf8mb4_polish_ci";
        case UTF8MB4_ESTONIAN_CI: return "utf8mb4 COLLATE utf8mb4_estonian_ci";
        case UTF8MB4_SPANISH_CI: return "utf8mb4 COLLATE utf8mb4_spanish_ci";
        case UTF8MB4_SWEDISH_CI: return "utf8mb4 COLLATE utf8mb4_swedish_ci";
        case UTF8MB4_TURKISH_CI: return "utf8mb4 COLLATE utf8mb4_turkish_ci";
        case UTF8MB4_CZECH_CI: return "utf8mb4 COLLATE utf8mb4_czech_ci";
        case UTF8MB4_DANISH_CI: return "utf8mb4 COLLATE utf8mb4_danish_ci";
        case UTF8MB4_LITHUANIAN_CI: return "utf8mb4 COLLATE utf8mb4_lithuanian_ci";
        case UTF8MB4_SLOVAK_CI: return "utf8mb4 COLLATE utf8mb4_slovak_ci";
        case UTF8MB4_SPANISH2_CI: return "utf8mb4 COLLATE utf8mb4_spanish2_ci";
        case UTF8MB4_ROMAN_CI: return "utf8mb4 COLLATE utf8mb4_roman_ci";
        case UTF8MB4_PERSIAN_CI: return "utf8mb4 COLLATE utf8mb4_persian_ci";
        case UTF8MB4_ESPERANTO_CI: return "utf8mb4 COLLATE utf8mb4_esperanto_ci";
        case UTF8MB4_HUNGARIAN_CI: return "utf8mb4 COLLATE utf8mb4_hungarian_ci";
        case UTF8MB4_SINHALA_CI: return "utf8mb4 COLLATE utf8mb4_sinhala_ci";
        case UTF8MB4_GERMAN2_CI: return "utf8mb4 COLLATE utf8mb4_german2_ci";
        case UTF8MB4_CROATIAN_CI: return "utf8mb4 COLLATE utf8mb4_croatian_ci";
        case UTF8MB4_UNICODE_520_CI: return "utf8mb4 COLLATE utf8mb4_unicode_520_ci";
        case UTF8MB4_VIETNAMESE_CI: return "utf8mb4 COLLATE utf8mb4_vietnamese_ci";
        default:  return "Unkonwn";
        }
    }
    
    enum MySQLPacketType
    {
        UNKNOWN_PACKET,
        HANDSHAKE_REQUEST_V10,
        HANDSHAKE_REQUEST_V9,
        HANDSHAKE_RESPONSE_41,
        COMMAND_REQUEST,
        COLUMN_DEFINITION_41,
        OK_RESPONSE,
        ERR_RESPONSE,
        EOF_RESPONSE,
        TEXT_RESULT_ROW,
        COLUMN_COUNT
    };

    MySQLDecodedObject()
    {
        type = UNKNOWN_PACKET;
    }

    MySQLPacketHeader header;
    MySQLPacketType type;
    
    HandshakeRequestV10 handshake_req;
    HandshakeResponse41 handshake_rsp;
    CommandRequest cmd_req;
    OKResponse ok_rsp;
    ERRResponse err_rsp;
    EOFResponse eof_rsp;
    ColumnDefinition41 column;
    TextResultRow row;
    ColumnCount column_count;
};

class MySQLPacket
{
public:
    MySQLPacket(const uint8_t * pData, uint32_t nLength);
    ~MySQLPacket(void);

public:
    bool Decode(void);
    std::vector<MySQLDecodedObject*>& GetDecodedObject() { return m_objects; }

private:
    bool ParseObject(uint32_t nOffset, MySQLDecodedObject* pObject, bool bTextRow = false);

    bool ParseHeader(uint32_t nOffset, MySQLPacketHeader* pHeader);
    bool ParseCommandRequest(uint32_t nOffset, uint32_t nPayloadLen, CommandRequest* pRequest);

    bool ParseHandshakeRequestV10(uint32_t nOffset, HandshakeRequestV10* pRequest);
    bool ParseHandshakeResponse41(uint32_t nOffset, uint32_t nPayloadLen, HandshakeResponse41* pResponse);

    bool ParseOKResponse(uint32_t nOffset, uint32_t nPayloadLen, OKResponse* pResponse);
    bool ParseErrorResponse(uint32_t nOffset, ERRResponse* pResponse);
    bool ParseEOFResponse(uint32_t nOffset, uint32_t nPayloadLen, EOFResponse* pResponse);

    bool ParseColumnCount(uint32_t nOffset, ColumnCount* pCount);
    bool ParseTextResultRow(uint32_t nOffset, uint32_t nPayloadLen, TextResultRow* pRow);
    bool ParseColumnDefinition41(uint32_t nOffset, uint32_t nPayloadLen, ColumnDefinition41* pColumn);
    bool ParseChangeUser(uint32_t& nOffset, CommandRequest* pRequest);

    bool ParseLenencInteger(uint32_t nOffset, LengthEncodedInteger& data);
    bool ParseNULString(uint32_t nOffset, NULString& data, uint32_t* pDataLen);
    bool ParseEOFString(uint32_t nOffset, EOFString& data, uint32_t* pDataLen);
    bool ParseFIXString(uint32_t nOffset, uint32_t nLen, FIXString& data);
    bool ParseLenencString(uint32_t nOffset, LengthEncodedString& data);

private:
    const uint8_t * m_pData;
    uint32_t m_nLength;

    std::vector<MySQLDecodedObject*> m_objects;
};

#endif
/**
 * @file      mysql.cpp
 * @brief     Mysql Protocol
 * @author    yangyiyin
 * @date      2021/12
 * @copyright
 */

#include <string.h>
#include "mysql.h"

MySQLPacket::MySQLPacket(const uint8_t * pData, uint32_t nLength)
    : m_pData(pData)
    , m_nLength(nLength)
{
}

MySQLPacket::~MySQLPacket(void)
{
}

bool MySQLPacket::Decode(void)
{
    if(m_nLength < MYSQL_PACKET_HEADER_LENGTH)
    {
        return false;
    }

    uint32_t nOffset = 0;
    MySQLDecodedObject* pObject = NULL;
    uint8_t nTextRowFlag = 0;

    while (nOffset < m_nLength)
    {
        pObject = new MySQLDecodedObject;       

        if (!ParseObject(nOffset, pObject, 0x07 == nTextRowFlag))
        {
            delete pObject;
        }
        else
        {
            m_objects.push_back(pObject);
        }

        switch (pObject->type)
        {
        case MySQLDecodedObject::COLUMN_COUNT:
            nTextRowFlag |= 0x01;
            break;
        case MySQLDecodedObject::COLUMN_DEFINITION_41:
            nTextRowFlag |= 0x02;
            break;
        case MySQLDecodedObject::EOF_RESPONSE:
            nTextRowFlag |= 0x04;
            break;
        default:
            break;
        }

        nOffset += (MYSQL_PACKET_HEADER_LENGTH + pObject->header.payload_len);
    }
        
    return true;
}

bool MySQLPacket::ParseObject(uint32_t nOffset, MySQLDecodedObject* pObject, bool bTextRow)
{
    if (!ParseHeader(nOffset, &pObject->header))
    {
        return false;
    }

    nOffset += MYSQL_PACKET_HEADER_LENGTH;
    
    if (pObject->header.payload_len >= MYSQL_OK_PACKET_LENGTH_MIN && (MYSQL_OK_PACKET_HEADER_1 == m_pData[nOffset]))
    {
        if (ParseOKResponse(nOffset, pObject->header.payload_len, &pObject->ok_rsp))
        {
            pObject->type = MySQLDecodedObject::MySQLPacketType::OK_RESPONSE;
            return true;
        }
    }

    if (pObject->header.payload_len >= MYSQL_ERROR_PACKET_LENGTH_MIN && MYSQL_ERR_PACKET_HEADER == m_pData[nOffset])
    {
        if (ParseErrorResponse(nOffset, &pObject->err_rsp))
        {
            pObject->type = MySQLDecodedObject::MySQLPacketType::ERR_RESPONSE;
            return true;
        }
    }

    if (MYSQL_EOF_PACKET_HEADER == m_pData[nOffset])
    {
        if (ParseEOFResponse(nOffset, pObject->header.payload_len, &pObject->eof_rsp))
        {
            pObject->type = MySQLDecodedObject::MySQLPacketType::EOF_RESPONSE;
            return true;
        }
    }

    if (1 == pObject->header.payload_len && m_nLength - nOffset != pObject->header.payload_len)
    {
        if (ParseColumnCount(nOffset, &pObject->column_count))
        {
            pObject->type = MySQLDecodedObject::MySQLPacketType::COLUMN_COUNT;
            return true;
        }
    }

    if (!bTextRow && ParseColumnDefinition41(nOffset, pObject->header.payload_len, &pObject->column))
    {
        pObject->type = MySQLDecodedObject::MySQLPacketType::COLUMN_DEFINITION_41;
        return true;
    }

    if (pObject->header.payload_len >= MYSQL_HANDSHAKE_REQUEST_V10_LENGTH_MIN && MYSQL_HANDSHAKE_PROTOCOL_V10 == m_pData[nOffset])
    {
        if (ParseHandshakeRequestV10(nOffset, &pObject->handshake_req))
        {
            pObject->type = MySQLDecodedObject::MySQLPacketType::HANDSHAKE_REQUEST_V10;
            return true;
        }
    }

    if (ParseCommandRequest(nOffset, pObject->header.payload_len, &pObject->cmd_req))
    {
        pObject->type = MySQLDecodedObject::MySQLPacketType::COMMAND_REQUEST;
        return true;
    }

    if (pObject->header.payload_len >= MYSQL_HANDSHAKE_RESPONSE_41_LENGTH_MIN)
    {
        if (ParseHandshakeResponse41(nOffset, pObject->header.payload_len, &pObject->handshake_rsp))
        {
            pObject->type = MySQLDecodedObject::MySQLPacketType::HANDSHAKE_RESPONSE_41;
            return true;
        }
    }
    else if (ParseTextResultRow(nOffset, pObject->header.payload_len, &pObject->row))
    { 
        pObject->type = MySQLDecodedObject::MySQLPacketType::TEXT_RESULT_ROW;
        return true;
    }

    return false;
}

bool MySQLPacket::ParseHeader(uint32_t nOffset, MySQLPacketHeader* pHeader)
{
    if (nOffset + MYSQL_PACKET_HEADER_LENGTH >= m_nLength)
    {
        return false;
    }

    pHeader->pdu = (MySQLPacketHeaderPDU*)(m_pData + nOffset);
    pHeader->payload_len = (*(uint32_t*)(pHeader->pdu->payload_len));
    pHeader->payload_len &= 0x00ffffff;
    pHeader->sequence_id = pHeader->pdu->sequence_id;

    return true;
}

bool MySQLPacket::ParseCommandRequest(uint32_t nOffset, uint32_t nPayloadLen, CommandRequest* pRequest)
{
    uint32_t nStartOffset = nOffset;
    uint32_t nLen = 0;

    pRequest->command = m_pData[nOffset++];

    switch (pRequest->command)
    {
    case MYSQL_COM_INIT_DB:
        if (!ParseEOFString(nOffset, pRequest->initDB.schema_name, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;

    case MYSQL_COM_QUERY:
        if(!ParseEOFString(nOffset, pRequest->query.statement, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;

    case MYSQL_COM_FIELD_LIST:
        if (!ParseNULString(nOffset, pRequest->fieldList.table, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        
        if (!ParseEOFString(nOffset, pRequest->fieldList.field_wildcard, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;

    case MYSQL_COM_CREATE_DB:
        if(!ParseEOFString(nOffset, pRequest->createDB.schema_name, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;

    case MYSQL_COM_DROP_DB:
        if(!ParseEOFString(nOffset, pRequest->dropDB.schema_name, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;

    case MYSQL_COM_REFRESH:
        pRequest->refresh.sub_command = m_pData[nOffset++];
        break;

    case MYSQL_COM_SHUTDOWN:
        pRequest->shutdown.shutdown_type = m_pData[nOffset++];
        break;

    case MYSQL_COM_PROCESS_KILL:
        pRequest->processKill.connection_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        break;

    case MYSQL_COM_CHANGE_USER:
        if (!ParseChangeUser(nOffset, pRequest))
        {
            return false;
        }
        break;

    case MYSQL_COM_BINLOG_DUMP:
        pRequest->binLogDump.binlog_pos = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        pRequest->binLogDump.flags = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;
        pRequest->binLogDump.server_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;

        if(!ParseEOFString(nOffset, pRequest->binLogDump.binlog_file_name, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;

    case MYSQL_COM_BINLOG_DUMP_GTID:
        pRequest->binLogDumpGTID.flags = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;
        pRequest->binLogDumpGTID.server_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        pRequest->binLogDumpGTID.binlog_filename_len = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        
        if (!ParseFIXString(nOffset, pRequest->binLogDumpGTID.binlog_filename_len, pRequest->binLogDumpGTID.binlog_filename))
        {
            return false;
        }

        nOffset += pRequest->binLogDumpGTID.binlog_filename_len;

        pRequest->binLogDumpGTID.binlog_pos = *(uint64_t*)(m_pData + nOffset);
        nOffset += 8;

        if (nOffset - nStartOffset < nPayloadLen && pRequest->binLogDumpGTID.flags & BINLOG_THROUGH_GTID)
        {
            pRequest->binLogDumpGTID.data_size = *(uint32_t*)(m_pData + nOffset);
            nOffset += 4;

            if (!ParseFIXString(nOffset, pRequest->binLogDumpGTID.data_size, pRequest->binLogDumpGTID.data))
            {
                return false;
            }

            nOffset += pRequest->binLogDumpGTID.data_size;
        }
        break;

    case MYSQL_COM_TABLE_DUMP:
        pRequest->tableDump.database_len = m_pData[nOffset++];

        if (!ParseFIXString(nOffset, pRequest->tableDump.database_len, pRequest->tableDump.database_name))
        {
            return false;
        }

        nOffset += pRequest->tableDump.database_len;
        pRequest->tableDump.table_len = m_pData[nOffset++];

        if(!ParseFIXString(nOffset, pRequest->tableDump.table_len, pRequest->tableDump.table_name))
        {
            return false;
        }

        nOffset += pRequest->tableDump.table_len;
        break;

    case MYSQL_COM_REGISTER_SLAVE:
        pRequest->registerSlave.server_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        
        pRequest->registerSlave.hostname_len = m_pData[nOffset++];
        if (!ParseFIXString(nOffset, pRequest->registerSlave.hostname_len, pRequest->registerSlave.hostname))
        {
            return false;
        }

        nOffset += pRequest->registerSlave.hostname_len;

        pRequest->registerSlave.user_len = m_pData[nOffset++];
        if (!ParseFIXString(nOffset, pRequest->registerSlave.user_len, pRequest->registerSlave.user))
        {
            return false;
        }

        nOffset += pRequest->registerSlave.user_len;

        pRequest->registerSlave.password_len = m_pData[nOffset++];
        if (!ParseFIXString(nOffset, pRequest->registerSlave.password_len, pRequest->registerSlave.password))
        {
            return false;
        }

        nOffset += pRequest->registerSlave.password_len;

        pRequest->registerSlave.port = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;

        pRequest->registerSlave.replication_rank = *(uint32_t*)(m_pData + nOffset); 
        nOffset += 4;

        pRequest->registerSlave.master_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        break;

    case MYSQL_COM_STMT_PREPARE:
        if (!ParseEOFString(nOffset, pRequest->stmtPrepare.query, &nLen))
        {
            return false;
        }

        nOffset += nLen;
        break;
    
    case MYSQL_COM_STMT_SEND_LONG_DATA:
        pRequest->stmtSendData.statement_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        pRequest->stmtSendData.param_id = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;

        if (!ParseEOFString(nOffset, pRequest->stmtSendData.data, &nLen))
        {
            return false;
        }
        nOffset += nLen;
        break;

    case MYSQL_COM_STMT_CLOSE:
        pRequest->stmtClose.statement_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        break;

    case MYSQL_COM_STMT_RESET:
        pRequest->stmtReset.statement_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        break;

    case MYSQL_COM_STMT_FETCH:
        pRequest->stmtFetch.statement_id = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        pRequest->stmtFetch.num_rows = *(uint32_t*)(m_pData + nOffset);
        nOffset += 4;
        break;

    case MYSQL_COM_SET_OPTION:
        pRequest->setOption.option_operation = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;
        break;

    case MYSQL_COM_SEMI_SYNC:
        pRequest->semiSync.flag = m_pData[nOffset++];
        break;

    default:
        break;
    }

    return nOffset - nStartOffset == nPayloadLen;
}

bool MySQLPacket::ParseHandshakeRequestV10(uint32_t nOffset, HandshakeRequestV10* pRequest)
{
    pRequest->protocol_version = m_pData[nOffset];
    nOffset++;

    uint32_t nLen = 0;
    if (!ParseNULString(nOffset, pRequest->server_version, &nLen))
    {
        return false;
    }
    nOffset += nLen;

    pRequest->connection_id = *(uint32_t*)(m_pData + nOffset);
    nOffset += 4;

    if (!ParseFIXString(nOffset, MYSQL_HANDSHAKE_APDP_1_LENGTH, pRequest->auth_plugin_data_part1))
    {
        return false;
    }
    nOffset += MYSQL_HANDSHAKE_APDP_1_LENGTH;

    // filler
    nOffset++;

    pRequest->capablity_flags_lower = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    // 没有更多的数据
    if (nOffset >= m_nLength)
    {
        return true;
    }
    
    pRequest->character_set = m_pData[nOffset++];

    pRequest->status_flag = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    pRequest->capablity_flags_upper = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    pRequest->auth_plugin_data_len = m_pData[nOffset++];

    pRequest->reserved.assign((char*)m_pData + nOffset, MYSQL_HANDSHAKE_RESERVED_LENGTH);
    nOffset += MYSQL_HANDSHAKE_RESERVED_LENGTH;

    if (pRequest->CapabilityFlagsBit(CLIENT_SECURE_CONNECTION))
    {
        if (pRequest->auth_plugin_data_len <= MYSQL_HANDSHAKE_APDP_2_LENGTH_MIN + MYSQL_HANDSHAKE_APDP_1_LENGTH)
        {
            nLen = MYSQL_HANDSHAKE_APDP_2_LENGTH_MIN;
        }
        else
        {
            nLen = pRequest->auth_plugin_data_len - 8;
        }

        if (!ParseFIXString(nOffset, nLen, pRequest->auth_plugin_data_part2))
        {
            return false;
        }

        nOffset += nLen;
    }
    
    if (pRequest->CapabilityFlagsBit(CLIENT_PLUGIN_AUTH))
    {
        if (!ParseEOFString(nOffset, pRequest->auth_plugin_name, &nLen))
        {
            return false;
        }
    }

    return true;
}

bool MySQLPacket::ParseChangeUser(uint32_t& nOffset, CommandRequest* pRequest)
{
    uint32_t nLen = 0;

    if (!ParseNULString(nOffset, pRequest->changeUser.user, &nLen))
    {
        return false;
    }
    nOffset += nLen;

    // 8.x, 5.X
    pRequest->changeUser.auth_response_len = m_pData[nOffset++];

    if (!ParseFIXString(nOffset, pRequest->changeUser.auth_response_len, pRequest->changeUser.auth_response))
    {
        return false;
    }
    nOffset += pRequest->changeUser.auth_response_len;

    if (!ParseNULString(nOffset, pRequest->changeUser.schema_name, &nLen))
    {
        return false;
    }
    nOffset += nLen;

    // 8.x
    if (nOffset < m_nLength)
    {
        pRequest->changeUser.character_set = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;

        if (!ParseNULString(nOffset, pRequest->changeUser.auth_plugin_name, &nLen))
        {
            return false;
        }
        nOffset += nLen;

        if (!ParseLenencInteger(nOffset, pRequest->changeUser.attr_len))
        {
            return false;
        }

        nOffset += pRequest->changeUser.attr_len.length;

        uint64_t n = 0;
        while (n < pRequest->changeUser.attr_len.value && nOffset < m_nLength)
        {
            MySQLConnectAttribute attr;
            
            if (!ParseLenencString(nOffset, attr.key))
            {
                return false;
            }

            nOffset += attr.key.Length();
            n += attr.key.Length();

            if (!ParseLenencString(nOffset, attr.value))
            {
                return false;
            }

            nOffset += attr.value.Length();
            n += attr.key.Length();

            pRequest->changeUser.attrs.push_back(attr);
        }
    }

    return true;
}

bool MySQLPacket::ParseHandshakeResponse41(uint32_t nOffset, uint32_t nPayloadLen, HandshakeResponse41* pResponse)
{
    uint32_t nStartOffset = nOffset;

    pResponse->capability_flags = *(uint32_t*)(m_pData + nOffset);
    nOffset += 4;

    pResponse->max_packet_size = *(uint32_t*)(m_pData + nOffset);
    nOffset += 4;

    pResponse->character_set = m_pData[nOffset++];

    pResponse->reserved.assign((char*)m_pData + nOffset, MYSQL_HANDSHAKE_RESPONSE_41_RESERVED_LENGTH);
    nOffset += MYSQL_HANDSHAKE_RESPONSE_41_RESERVED_LENGTH;

    // 有的版本握手应答不包含username及之后的字段
    if (nOffset >= m_nLength && (nOffset - nStartOffset) == nPayloadLen)
    {
        return true;
    }

    uint32_t nLen = 0;
    if (!ParseNULString(nOffset, pResponse->username, &nLen))
    {
        return false;
    }

    nOffset += nLen;

    if (pResponse->CapabilityFlagsBit(CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA))
    {
        if (!ParseLenencInteger(nOffset, pResponse->auth_response_len))
        {
            return false;
        }
                
        nOffset += pResponse->auth_response_len.length;

        if (!ParseFIXString(nOffset, pResponse->auth_response_len.value, pResponse->auth_response))
        {
            return false;
        }

        nOffset += pResponse->auth_response_len.value;
    }
    else if (pResponse->CapabilityFlagsBit(CLIENT_SECURE_CONNECTION))
    {
        pResponse->auth_response_len.length = 1;
        pResponse->auth_response_len.value = m_pData[nOffset];
        nOffset += pResponse->auth_response_len.length;

        if (!ParseFIXString(nOffset, pResponse->auth_response_len.value, pResponse->auth_response))
        {
            return false;
        }

        nOffset += pResponse->auth_response_len.value;
    }
    else
    {
        if (!ParseNULString(nOffset, pResponse->auth_response, &nLen))
        {
            return false;
        }

        nOffset += nLen;
    }

    if (pResponse->CapabilityFlagsBit(CLIENT_CONNECT_WITH_DB))
    {
        if (!ParseNULString(nOffset, pResponse->database, &nLen))
        {
            return false;
        }

        nOffset += nLen;
    }

    if (nOffset < m_nLength && pResponse->CapabilityFlagsBit(CLIENT_PLUGIN_AUTH))
    {
        if (!ParseNULString(nOffset, pResponse->auth_plugin_name, &nLen))
        {
            return false;
        }

        nOffset += nLen;
    }

    if (nOffset < m_nLength && pResponse->CapabilityFlagsBit(CLIENT_CONNECT_ATTRS))
    {
        if (!ParseLenencInteger(nOffset, pResponse->attr_len))
        {
            return false;
        }

        nOffset += pResponse->attr_len.length;
        uint64_t n = 0;

        while (n < pResponse->attr_len.value && nOffset < m_nLength)
        {
            MySQLConnectAttribute attr;

            if (!ParseLenencString(nOffset, attr.key))
            {
                return false;
            }
            
            nOffset += attr.key.Length();
            n += attr.key.Length();

            if (!ParseLenencString(nOffset, attr.value))
            {
                return false;
            }

            nOffset += attr.value.Length();
            n += attr.value.Length();

            pResponse->attrs.push_back(attr);
        }
    }
    
    return nOffset - nStartOffset == nPayloadLen;
}

bool MySQLPacket::ParseOKResponse(uint32_t nOffset, uint32_t nPayloadLen, OKResponse* pResponse)
{
    uint32_t nStartOffset = nOffset;
    pResponse->header = m_pData[nOffset++];

    if (!ParseLenencInteger(nOffset, pResponse->affected_rows))
    {
        return false;
    }

    nOffset += pResponse->affected_rows.length;

    if (!ParseLenencInteger(nOffset, pResponse->last_insert_id))
    {
        return false;
    }

    nOffset += pResponse->last_insert_id.length;

    pResponse->status_flag = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    pResponse->warnings = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    /* 单包无法判断capability flag的CLIENT_SESSION_TRACK比特,没有多余的数据就停止解析 */
    if (nOffset >= m_nLength && (nOffset - nStartOffset) == nPayloadLen)
    {
        return true;
    }

    if (!ParseLenencString(nOffset, pResponse->status_info))
    {
        return false;
    }

    nOffset += pResponse->status_info.Length();

    // 没有Session状态变化数据
    if ((nOffset >= m_nLength || !pResponse->StatusFlagBit(SERVER_SESSION_STATE_CHANGED)) && (nOffset - nStartOffset) == nPayloadLen)
    {
        return true;
    }

    if (!ParseLenencString(nOffset, pResponse->state_changes.infos_str))
    {
        return false;
    }

    nOffset += pResponse->state_changes.infos_str.length.length;
    pResponse->state_changes.infos.type = m_pData[nOffset++];
    
    if(!ParseLenencString(nOffset, pResponse->state_changes.infos.data_str))
    {
        return false;
    }

    nOffset += pResponse->state_changes.infos.data_str.length.length;

    switch (pResponse->state_changes.infos.type)
    {
    case SESSION_TRACK_SYSTEM_VARIABLES:
        if (!ParseLenencString(nOffset, pResponse->state_changes.infos.data.track_sys_var.name))
        {
            return false;
        }

        nOffset += pResponse->state_changes.infos.data.track_sys_var.name.Length();

        if (!ParseLenencString(nOffset, pResponse->state_changes.infos.data.track_sys_var.value))
        {
            return false;
        }
                
        break;

    case SESSION_TRACK_SCHEMA:
        if (!ParseLenencString(nOffset, pResponse->state_changes.infos.data.track_schema.name))
        {
            return false;
        }
        break;

    case SESSION_TRACK_STATE_CHANGE:
        if (!ParseLenencString(nOffset, pResponse->state_changes.infos.data.track_state_change.is_tracked))
        {
            return false;
        }
        break;

    case SESSION_TRACK_GTIDS:
        if (!ParseLenencString(nOffset, pResponse->state_changes.infos.data.track_gtids.grids))
        {
            return false;
        }
        break;

    default:
        break;
    }

    return (nOffset - nStartOffset) == nPayloadLen;
}

bool MySQLPacket::ParseErrorResponse(uint32_t nOffset, ERRResponse* pResponse)
{
    pResponse->header = m_pData[nOffset++];

    pResponse->error_code = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    pResponse->sql_state_marker = m_pData[nOffset++];
    pResponse->sql_state.assign((char*)m_pData + nOffset, MYSQL_ERROR_PACKET_SQL_STATE_LEN);

    uint32_t nLen = 0;
    return ParseEOFString(nOffset, pResponse->error_msg, &nLen);
}

bool MySQLPacket::ParseEOFResponse(uint32_t nOffset, uint32_t nPayloadLen, EOFResponse* pResponse)
{   
    pResponse->header = m_pData[nOffset++];

    if (nOffset + 2 <= m_nLength)
    {
        pResponse->warnings = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;
    }

    if (nOffset + 2 <= m_nLength)
    {
        pResponse->status_flags = *(uint16_t*)(m_pData + nOffset);
        nOffset += 2;
    }

    return nOffset <= m_nLength;
}

bool MySQLPacket::ParseColumnCount(uint32_t nOffset, ColumnCount* pCount)
{
    if (!ParseLenencInteger(nOffset, pCount->column_count))
    {
        return false;
    }

    return true;
}

bool MySQLPacket::ParseTextResultRow(uint32_t nOffset, uint32_t nPayloadLen, TextResultRow* pRow)
{
    if (m_pData[nOffset] == MYSQL_TEXT_RESULT_RAW)
    {
        pRow->null = true;
        return true;
    }
    
    uint32_t nTmp = 0;
    uint64_t nLen = 0;
    pRow->null = false;

    while (nTmp < nPayloadLen)
    {
        LengthEncodedString text;

        if (!ParseLenencString(nOffset, text))
        {
            return false;
        }

        pRow->texts.push_back(text);

        nLen = text.Length();
        nTmp += nLen;
        nOffset += nLen;
    }

    return nTmp == nPayloadLen;
}

bool MySQLPacket::ParseColumnDefinition41(uint32_t nOffset, uint32_t nPayloadLen, ColumnDefinition41* pColumn)
{
    uint32_t nStartOffset = nOffset;
    uint64_t nLen = 0;

    std::vector<LengthEncodedString*> strs;
    strs.push_back(&pColumn->catalog);
    strs.push_back(&pColumn->schema);
    strs.push_back(&pColumn->table);
    strs.push_back(&pColumn->org_table);
    strs.push_back(&pColumn->name);
    strs.push_back(&pColumn->org_name);

    for (auto& iter : strs)
    {
        if (!ParseLenencString(nOffset, *iter))
        {
            return false;
        }

        nLen = iter->Length();
        nOffset += nLen;

        if (nOffset - nStartOffset == nPayloadLen)
        {
            return true;
        }
    }
    
    if (!ParseLenencInteger(nOffset, pColumn->fixed_fields_len))
    {
        return false;
    }

    nOffset += pColumn->fixed_fields_len.length;

    pColumn->character_set = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    pColumn->column_len = *(uint32_t*)(m_pData + nOffset);
    nOffset += 4;

    pColumn->type = m_pData[nOffset++];

    pColumn->flags = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    pColumn->decimals = m_pData[nOffset++];

    pColumn->filler = *(uint16_t*)(m_pData + nOffset);
    nOffset += 2;

    if (nOffset - nStartOffset < nPayloadLen)
    {
        // 当表字段的默认值为NULL时使用0xfb表示
        if (m_pData[nOffset] == 0xfb)
        {
            pColumn->default_values_len.value = 4;
            pColumn->default_values.assign("NULL");
            return true;
        }
        
        if (!ParseLenencInteger(nOffset, pColumn->default_values_len))
        {
            return false;
        }

        nOffset += pColumn->default_values_len.length;

        if (!ParseFIXString(nOffset, pColumn->default_values_len.value, pColumn->default_values))
        {
            return false;
        }
    }

    return nOffset - nStartOffset == nPayloadLen;
}

bool MySQLPacket::ParseLenencInteger(uint32_t nOffset, LengthEncodedInteger& data)
{
    data.prefix = 0;
    data.length = 0;

    switch (m_pData[nOffset])
    {
    case 0xfc:
        data.prefix = m_pData[nOffset++];
        data.length = 3;
        data.value = *(uint16_t*)(m_pData + nOffset);
        break;

    case 0xfd:
        data.prefix = m_pData[nOffset++];
        data.length = 4;
        data.value = *(uint32_t*)(m_pData + nOffset);
        data.value &= 0x00ffffff;
        break;

    case 0xfe:
        data.prefix = m_pData[nOffset++];
        data.length = 9;
        data.value = *(uint64_t*)(m_pData + nOffset);
        break;

    default:
        if (m_pData[nOffset] < 251)
        {
            data.length = 1;
            data.value = m_pData[nOffset];
        }
        break;
    }

    nOffset += data.length;
    return data.length > 0 && nOffset <= m_nLength;
}

bool MySQLPacket::ParseNULString(uint32_t nOffset, NULString& data, uint32_t* pDataLen)
{
    uint32_t nStart = nOffset;

    while (m_pData[nOffset] && nOffset < m_nLength)
    {
        nOffset++;
    }
    
    nOffset++; //0x00
    *pDataLen = nOffset - nStart;

    if (nOffset <= m_nLength)
    {
        data.assign((char*)m_pData + nStart, *pDataLen);
    }

    return nOffset <= m_nLength;
}

bool MySQLPacket::ParseEOFString(uint32_t nOffset, EOFString& data, uint32_t* pDataLen)
{
    if (nOffset < m_nLength)
    {
        *pDataLen = m_nLength - nOffset;
        data.assign((char*)m_pData + nOffset, *pDataLen);
    }
        
    return nOffset <= m_nLength;
}

bool MySQLPacket::ParseFIXString(uint32_t nOffset, uint32_t nLen, FIXString& data)
{
    data.assign((char*)m_pData + nOffset, nLen);
    return nOffset + nLen <= m_nLength;
}

bool MySQLPacket::ParseLenencString(uint32_t nOffset, LengthEncodedString& data)
{
    if (!ParseLenencInteger(nOffset, data.length))
    {
        return false;
    }

    nOffset += data.length.length;

    if (!ParseFIXString(nOffset, data.length.value, data.value))
    {
        return false;
    }

    nOffset += data.length.value;
    return nOffset <= m_nLength;
}

        

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兔子要咬手指

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值