Mysql Resultset 解析记录

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 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/962053.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Deep Seek R1本地化部署

目录 说明 一、下载ollama 二、在ollama官网下载模型 三、使用 后记 说明 操作系统&#xff1a;win10 使用工具&#xff1a;ollama 一、下载ollama 从官网下载ollama&#xff1a; ollama默认安装在C盘&#xff0c;具体位置为C:\Users\用户名\AppData\Local\Programs\O…

跟李沐学AI:视频生成类论文精读(Movie Gen、HunyuanVideo)

Movie Gen&#xff1a;A Cast of Media Foundation Models 简介 Movie Gen是Meta公司提出的一系列内容生成模型&#xff0c;包含了 3.2.1 预训练数据 Movie Gen采用大约 100M 的视频-文本对和 1B 的图片-文本对进行预训练。 图片-文本对的预训练流程与Meta提出的 Emu: Enh…

Java---入门基础篇(上)

前言 本片文章主要讲了刚学Java的一些基础内容,例如注释,标识符,数据类型和变量,运算符,还有逻辑控制等,记录的很详细,带你从简单的知识点再到练习题.如果学习了c语言的小伙伴会发现,这篇文章的内容和c语言大致相同. 而在下一篇文章里,我会讲解方法和数组的使用,也是Java中基础…

3、C#基于.net framework的应用开发实战编程 - 实现(三、三) - 编程手把手系列文章...

三、 实现&#xff1b; 三&#xff0e;三、编写应用程序&#xff1b; 此文主要是实现应用的主要编码工作。 1、 分层&#xff1b; 此例子主要分为UI、Helper、DAL等层。UI负责便签的界面显示&#xff1b;Helper主要是链接UI和数据库操作的中间层&#xff1b;DAL为对数据库的操…

Go学习:类型转换需注意的点 以及 类型别名

目录 1. 类型转换 2. 类型别名 1. 类型转换 在从前的学习中&#xff0c;知道布尔bool类型变量只有两种值true或false&#xff0c;C/C、Python、JAVA等编程语言中&#xff0c;如果将布尔类型bool变量转换为整型int变量&#xff0c;通常采用 “0为假&#xff0c;非0为真”的方…

爬虫基础(四)线程 和 进程 及相关知识点

目录 一、线程和进程 &#xff08;1&#xff09;进程 &#xff08;2&#xff09;线程 &#xff08;3&#xff09;区别 二、串行、并发、并行 &#xff08;1&#xff09;串行 &#xff08;2&#xff09;并行 &#xff08;3&#xff09;并发 三、爬虫中的线程和进程 &am…

V103开发笔记1-20250113

2025-01-13 一、应用方向分析 应用项目&#xff1a; PCBFLY无人机项目&#xff08;包括飞控和手持遥控器&#xff09;&#xff1b; 分析移植项目&#xff0c;应用外设资源包括&#xff1a; GPIO, PWM,USART,GPIO模拟I2C/SPI, ADC,DMA,USB等&#xff1b; 二、移植项目的基本…

AAPM:基于大型语言模型代理的资产定价模型,夏普比率提高9.6%

“AAPM: Large Language Model Agent-based Asset Pricing Models” 论文地址&#xff1a;https://arxiv.org/pdf/2409.17266v1 Github地址&#xff1a;https://github.com/chengjunyan1/AAPM 摘要 这篇文章介绍了一种利用LLM代理的资产定价模型&#xff08;AAPM&#xff09;…

新版231普通阿里滑块 自动化和逆向实现 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向过程 补环境逆向 部分补环境 …

Autosar-Os是怎么运行的?(时间保护)

写在前面&#xff1a; 入行一段时间了&#xff0c;基于个人理解整理一些东西&#xff0c;如有错误&#xff0c;欢迎各位大佬评论区指正&#xff01;&#xff01;&#xff01; 1.功能概述 AUTOSAR OS 的四大可定制类型凸显了时间保护&#xff08;Timing Protection&#xff09;…

vue框架技术相关概述以及前端框架整合

vue框架技术概述及前端框架整合 1 node.js 介绍&#xff1a;什么是node.js Node.js就是运行在服务端的JavaScript。 Node.js是一个事件驱动I/O服务端JavaScript环境&#xff0c;基于Google的V8引擎。 作用 1 运行java需要安装JDK&#xff0c;而Node.js是JavaScript的运行环…

玩转大语言模型——使用langchain和Ollama本地部署大语言模型

系列文章目录 玩转大语言模型——使用langchain和Ollama本地部署大语言模型 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 玩转大语言模型——使用GraphRAGOllama构建知识图谱 玩转大语言模型——完美解决Gra…

亚博microros小车-原生ubuntu支持系列:15 激光雷达巡逻

一 TF坐标转换 ros2 -5.1 坐标变化工具介绍_ros怎么发布坐标变化-CSDN博客 ros2笔记-5.3 C中地图坐标系变换_c变换坐标系-CSDN博客 header:stamp:sec: 1737893911nanosec: 912000000frame_id: odom_frame child_frame_id: base_footprint pose:pose:position:x: 0.053831271…

C++并发编程指南06

文章目录 4.4 简化代码与同步工具同步工具作为构建块 4.4.1 使用Future的函数化编程函数化编程简介C支持函数化编程 快速排序 - FP模式快速排序串行版快速排序并行版 spawn_task函数结论快速排序 - 串行版快速排序 - 并行版spawn_task函数使用 spawn_task 实现并行快速排序详细…

ios swift画中画技术尝试

继上篇&#xff1a;iOS swift 后台运行应用尝试失败-CSDN博客 为什么想到画中画&#xff0c;起初是看到后台模式里有一个picture in picture&#xff0c;去了解了后发现这个就是小窗口视频播放&#xff0c;方便用户执行多任务。看小窗口视频的同时&#xff0c;可以作其他的事情…

C++,STL 六大组件:容器、迭代器、算法、函数对象、适配器、分配器

文章目录 引言一、容器&#xff08;Containers&#xff09;主要分类 二、迭代器&#xff08;Iterators&#xff09;三、算法&#xff08;Algorithms&#xff09;四、函数对象&#xff08;Functors&#xff09;五、适配器&#xff08;Adapters&#xff09;六、分配器&#xff08…

STM32项目分享:智能鱼缸

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、包含内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; STM32智能鱼缸/水族箱 &#xff08;资料分享见文末…

基于MinIO的对象存储增删改查

MinIO是一个高性能的分布式对象存储服务。Python的minio库可操作MinIO&#xff0c;包括创建/列出存储桶、上传/下载/删除文件及列出文件。 查看帮助信息 minio.exe --help minio.exe server --help …

14-6-1C++STL的list

(一&#xff09;list容器的基本概念 list容器简介&#xff1a; 1.list是一个双向链表容器&#xff0c;可高效地进行插入删除元素 2.list不可以随机存取元素&#xff0c;所以不支持at.(pos)函数与[ ]操作符 &#xff08;二&#xff09;list容器头部和尾部的操作 list对象的默…

汽车网络信息安全-ISO/SAE 21434解析(中)

目录 第七章-分布式网络安全活动 1. 供应商能力评估 2. 报价 3. 网络安全职责界定 第八章-持续的网络安全活动 1. 网路安全监控 2. 网络安全事件评估 3. 漏洞分析 4. 漏洞管理 第九章-概念阶段 1. 对象定义 2. 网路安全目标 3. 网络安全概念 第十章 - 产品开发 第十…