Mysql Resultset 解析记录

news/2025/2/3 4:51:58/

Mysql Resultset 解析记录

  • 结果集消息头
  • 字段定义
  • 结果数据
  • 完整spicy文件

结果集消息头

消息头由消息体长度+消息序列号+消息体组成;消息头长度为3字节,消息序列号长度为1字节。
结果集的消息头消息体内容为结果集的列数。

结果集消息头的spicy1格式如下:

type header = unit {osize : uint8[3];seq : uint8;on %done {self.size = self.osize[2];self.size = self.size << 8;self.size = self.size + self.osize[1];self.size = self.size << 8;self.size = self.size + self.osize[0];}var size : uint32;
};

消息体的内容是结果集的列数,是一个整数;但是为了适配整数的范围,该参数采用了INT_ENC的表现形式,其定义格式如下:

type INT_ENC = unit {osize : uint8;i2 : uint16  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 252);i3 : uint8[3] if ((self.osize & 0xff) == 253); i8 : uint64  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 254);inull : uint8[0] if ((self.osize & 0xff) == 251); on osize {self.value = self.osize;}on i2 {self.value = self.i2;}on i3 {self.value = self.i3[2];self.value = self.value << 8;self.value = self.value +  self.i3[1];self.value = self.value << 8;self.value = self.value +  self.i3[0];}on i8 {self.value = self.i8;}on inull {self.value = 0;}var value : uint64;
};

从以上定义可知,当列数小于251时,该类型数据占用的字节即为1字节;但是更大后,会采用大于一个字节的方式进行处理;当中有一个特殊情况,当占用一个字节,且值为251时,表示的是一个无效值;(后面再定义结果集内容时会用到这个值
对于消息头的读取可以进行组合如下:

type COLUMN_SIZE  = unit(inout rs: mysql_rs) {size : INT_ENC { rs.column_size = self.size.value; }
};
public type mysql_rs = unit {head : header;hdata : bytes &size=self.head.size { self.s_col_size.write($$); }on %init {self.s_col_size.connect(new COLUMN_SIZE(self));}var column_size : uint64;sink s_col_size;

在组合中,用到了unit的参数传递了mysql_rs,同时采用了sink的方式对数据进行了一次传递;如此做主要是为了适配消息体后续可能得扩展;整体的格式不需要变化太大,只需要针对消息体进行更改即可;同时兼容性也会更强。
紧接着的是字段定义。

字段定义

每个字段的定义包括,字段头+字段体,字段头的定义与前面的header定义相同,而后定义的是字段的各个内容,包括catalog、database_name,table_name,orig_table_name,column_name, orig_column_name,字符集索引,字符集长度,列类型,列标识及列精度;其中catalog、database_name,table_name,orig_table_name,column_name, orig_column_name都是数据长度+数据内容的方式进行存储。所以字段的读取定义如下:

type column = unit {catalog_len : INT_ENC;:skip bytes &size=self.catalog_len.value;db_len : INT_ENC;db_name : bytes &size = self.db_len.value;tbl_len : INT_ENC;tbl_name : bytes &size = self.tbl_len.value;otbl_len : INT_ENC;otbl_name : bytes &size = self.otbl_len.value;col_len : INT_ENC;col_name : bytes &size = self.col_len.value;ocol_len : INT_ENC;ocol_name : bytes &size = self.ocol_len.value;: skip int8;collation_idx : int16;coll_len  : int32;col_type : int8;col_flag : int16;col_decimals : int8;:   skip bytes &eod;on %done {print "{database:%s, tbl_name:%s, otbl_name:%s, col_name:%s, ocol_name:%s, collation_idx:%x, col_type:%x, col_flag:%x, col_decimals:%x}" %(self.db_name, self.tbl_name, self.otbl_name, self.col_name, self.ocol_name, self.collation_idx, self.col_type, self.col_flag, self.col_decimals);}
};

其中因为catalog的内容定义恒为def,所以通过skip方式进行了忽略。同时其中col_flag的读取字段可能会是1字节也可能是2字节(会根据认证过程中包含的客户端的参数进行变换、此处为了简化直接定义成了2字节);
包含文件头的定义为:
type column_with_header = unit {
head :header;
data : bytes &size=self.head.size { self.b.write($$); }

on %init {self.b.connect(new column);
}sink b;

};

因为在前面的解析总,已经获取了字段数,所以需要将该结构定义成数组的形式

public type mysql_rs = unit {head : header;hdata : bytes &size=self.head.size { self.s_col_size.write($$); }columns : column_with_header[self.column_size ];on %init {self.s_col_size.connect(new COLUMN_SIZE(self));}var column_size : uint64;sink s_col_size;

定义完字段后,接下来接收的就是实际的结果数据了

结果数据

resultset的结果数据以每行的形式进行传输。
每行的开头是header结构体,后面的数据内容即为一行数据,由N(N为结果集的列数)个数据单元组成,每个数据单元的组成形式为INT_ENC+数据实体组成。其定义如下:

type element_value = unit(inout r: row) {size : INT_ENC;data : bytes &size = self.size.value;on %done {print "idx: (%d, %d), size:%d, values:%s" % (r.row_idx, r.col_idx, self.size.value, self.data);}
};

此处为了方便的识别当前元素所处的位置,将行列索引进行了输出。
此处对element_value实际的值为NULL、空字符串的差异进行简要的说明;如果为空字符串,则INT_ENC内容为0,表示长度为0;而如果实际值为NULL,正常内容长度也为0,但是不能区分是否为NULL,所以mysql使用了251这个特殊的数字,将元素定义为了NULL。所以为INT_ENC的中,如果返现第一个字节的内容为251,则会将最终的size置为0,同时其结果也是NULL,此处未做特殊处理,实际应用时,可以继续这个条件进行修正。
行数据定义如下:

type row = unit(r_idx: uint32, column_size : uint64) {eles : element_value(self)[column_size] foreach { self.col_idx = self.col_idx + 1; }on %init {self.row_idx = r_idx;self.col_idx = 0;}var row_idx : uint32;var col_idx : uint32;
};

行数据头+行数据的定义如下:

type row_with_head = unit(inout rs: mysql_rs, column_size :uint64) {head : header;data : bytes &size=self.head.size { if ( *self.data.at(0) == 0xfe) {rs.is_done = True;}if (!rs.is_done)self.b.write($$); }on head {print "head size: %d" % self.head.size;}on %init {self.b.connect(new row(rs.row_idx, column_size));}sink b;
};

因为行数据传输的时候,未包含实际的行数信息;所以需要有标识定义何时结束结果集的传输;此处演示我们采用了相对比较简单的方式,即判断数据开始的值为0xfe则认为数据传输截止了(实际上还有数据大小的判断进行组合判断对结果集是否已经完成得判断)。
所以最终结果集的定义如下:
public type mysql_rs = unit {
head : header;
hdata : bytes &size=self.head.size { self.s_col_size.write($$); }
columns : column_with_header[self.column_size ];
rows : row_with_head(self, self.column_size)[] foreach {
if (self.is_done == True) {
stop;
}
self.row_idx = self.row_idx + 1;
}
on %init {
self.is_done = False;
self.row_idx = 0;
self.s_col_size.connect(new COLUMN_SIZE(self));
}

var column_size : uint64;
var is_done : bool;
var row_idx : uint32;
sink s_col_size;

};

完整spicy文件

完整spicy文件内容如下:

module mysql;
import spicy;type INT_ENC = unit {osize : uint8;i2 : uint16  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 252);i3 : uint8[3] if ((self.osize & 0xff) == 253); i8 : uint64  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 254);inull : uint8[0] if ((self.osize & 0xff) == 251); on osize {self.value = self.osize;}on i2 {self.value = self.i2;}on i3 {self.value = self.i3[2];self.value = self.value << 8;self.value = self.value +  self.i3[1];self.value = self.value << 8;self.value = self.value +  self.i3[0];}on i8 {self.value = self.i8;}on inull {self.value = 0;}var value : uint64;
};type header = unit {osize : uint8[3];seq : uint8;on %done {self.size = self.osize[2];self.size = self.size << 8;self.size = self.size + self.osize[1];self.size = self.size << 8;self.size = self.size + self.osize[0];}var size : uint32;
};type column = unit {catalog_len : INT_ENC;:skip bytes &size=self.catalog_len.value;db_len : INT_ENC;db_name : bytes &size = self.db_len.value;tbl_len : INT_ENC;tbl_name : bytes &size = self.tbl_len.value;otbl_len : INT_ENC;otbl_name : bytes &size = self.otbl_len.value;col_len : INT_ENC;col_name : bytes &size = self.col_len.value;ocol_len : INT_ENC;ocol_name : bytes &size = self.ocol_len.value;: skip int8;collation_idx : int16;coll_len  : int32;col_type : int8;col_flag : int16;col_decimals : int8;:   skip bytes &eod;on %done {print "{database:%s, tbl_name:%s, otbl_name:%s, col_name:%s, ocol_name:%s, collation_idx:%x, col_type:%x, col_flag:%x, col_decimals:%x}" %(self.db_name, self.tbl_name, self.otbl_name, self.col_name, self.ocol_name, self.collation_idx, self.col_type, self.col_flag, self.col_decimals);}
};type column_with_header = unit {head :header;data : bytes &size=self.head.size { self.b.write($$); }on %init {self.b.connect(new column);}sink b;
};type element_value = unit(inout r: row) {size : INT_ENC;data : bytes &size = self.size.value;on %done {print "idx: (%d, %d), size:%d, values:%s" % (r.row_idx, r.col_idx, self.size.value, self.data);}
};type row = unit(r_idx: uint32, column_size : uint64) {eles : element_value(self)[column_size] foreach { self.col_idx = self.col_idx + 1; }on %init {self.row_idx = r_idx;self.col_idx = 0;}var row_idx : uint32;var col_idx : uint32;};type row_with_head = unit(inout rs: mysql_rs, column_size :uint64) {head : header;data : bytes &size=self.head.size { if ( *self.data.at(0) == 0xfe) {rs.is_done = True;}if (!rs.is_done)self.b.write($$); }on head {print "head size: %d" % self.head.size;}on %init {self.b.connect(new row(rs.row_idx, column_size));}sink b;
};type COLUMN_SIZE  = unit(inout rs: mysql_rs) {size : INT_ENC { rs.column_size = self.size.value; }
};public type mysql_rs = unit {head : header;hdata : bytes &size=self.head.size { self.s_col_size.write($$); }columns : column_with_header[self.column_size ];rows : row_with_head(self, self.column_size)[] foreach {if (self.is_done == True) {stop;}self.row_idx = self.row_idx + 1;}on %init {self.is_done = False;self.row_idx = 0;self.s_col_size.connect(new COLUMN_SIZE(self));}var column_size : uint64;var is_done : bool;var row_idx : uint32;sink s_col_size;};

假设文件存储名为mysql_rs.spicy,则可通过spicy-driver mysql_rs.spicy进行语法校验及调测。调测运行可以采用
printf “0x070x000x00…” | xxd -r -p | spicy-driver mysql_rs.spicy
进行调测输出。
其中xxd命令,主要是将16进制的字符串转换为二进制数。


  1. 1:spicy是zeek用于定义协议解析的语言,可参考https://zeek.org ↩︎


http://www.ppmy.cn/news/1568865.html

相关文章

Jenkins 的安装(详细教程)_jenkins安装

二、安装前准备 在安装 jenkins 之前要先确保电脑上是否已配置过 Java 的环境变量&#xff0c;可调出命令窗口&#xff08;win R 再输入 cmd&#xff09;&#xff0c;通过 java -version 来检验 如果没有显示 Java 的版本信息&#xff0c;就需要先配置 Java 环境变量&#xf…

大模型本地部署使用方法(Ollama脚手架工具、FisherAI浏览器大模型插件、AnythingLLM大模型集成应用平台)

一、Ollama &#xff08;一&#xff09;Ollama简介 Ollama是一个专为在本地环境中运行和定制大型语言模型而设计的工具。它提供简单高效的接口&#xff0c;用于创建、运行和管理这些模型&#xff0c;方便用户直接使用&#xff0c;也方便用作后台服务支撑其它应用程序。熟悉网…

Vue2.x简介

Vue2.x简介 Vue2.x的版本介绍Vue2.x的两大组件库 Vue2.x的版本介绍 Vue2.x是vue.js的第二个主要版本&#xff0c;最初版发布于2016 年&#xff0c;最终版发布于2023年12月24日&#xff08;版本号&#xff1a;2.7.16&#xff0c;版本名&#xff1a;Swan Song&#xff08;绝唱&a…

【Python】第七弹---Python基础进阶:深入字典操作与文件处理技巧

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】【Python】 目录 1、字典 1.1、字典是什么 1.2、创建字典 1.3、查找 key 1.4、新增/修改元素 1.5、删除元素 1.6、遍历…

【C++高并发服务器WebServer】-10:网络编程基础概述

本文目录 一、MAC地址二、IP地址三、子网掩码四、TCP/IP四层模型五、协议六、socket七、字节序 一、MAC地址 网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件&#xff0c;又称为网络适配器或网络接口卡NIC。其拥有 MAC 地址&#xff0c;属于 OSI模型的第2层…

[Collection与数据结构] B树与B+树

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

tf.Keras (tf-1.15)使用记录2-基于tf.keras.layers创建层

tf.keras.layers是keras的主要网络创建方法&#xff0c;里面已经有成熟的网络层&#xff0c;也可以通过继承的方式自定义神经网络层。 在keras的model定义中&#xff0c;为了保证所有对数据的操作都是可追溯、可保存、可反向传播&#xff0c;需要保证对数据的任何操作都是基于t…

Java数据结构和算法(一)

1、综述 1.1 数据结构和算法能起到什么作用&#xff1f; 现实世界的数据存储程序员的工具建模 1.2 数据结构的概述 1.1.png 1.1_2.png 1.3 算法的概述 对于大多数数据结构来说&#xff0c;都需要知道 插入一条新的数据项寻找某一特定的数据项删除某一特定的数据项 还需要知道如…