罪恶克星 发表于 2025-2-18 04:33:14

Mysql Resultset 解析记录

效果集消息头

消息头由消息体长度+消息序列号+消息体组成;消息头长度为3字节,消息序列号长度为1字节。
效果集的消息头消息体内容为效果集的列数。
效果集消息头的spicy1格式如下:
type header = unit {
    osize : uint8;
    seq : uint8;

    on %done {
      self.size = self.osize;
      self.size = self.size << 8;
      self.size = self.size + self.osize;
      self.size = self.size << 8;
      self.size = self.size + self.osize;

    }
    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 if ((self.osize & 0xff) == 253);
    i8 : uint64&byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 254);
    inull : uint8 if ((self.osize & 0xff) == 251);

    on osize {
      self.value = self.osize;
    }
    on i2 {
      self.value = self.i2;
    }

    on i3 {
      self.value = self.i3;
      self.value = self.value << 8;
      self.value = self.value +self.i3;
      self.value = self.value << 8;
      self.value = self.value +self.i3;
    }

    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;
        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) 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;
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 if ((self.osize & 0xff) == 253);
    i8 : uint64&byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 254);
    inull : uint8 if ((self.osize & 0xff) == 251);

    on osize {
      self.value = self.osize;
    }
    on i2 {
      self.value = self.i2;
    }

    on i3 {
      self.value = self.i3;
      self.value = self.value << 8;
      self.value = self.value +self.i3;
      self.value = self.value << 8;
      self.value = self.value +self.i3;
    }

    on i8 {
      self.value = self.i8;
    }
   
    on inull {
      self.value = 0;
    }

    var value : uint64;
};
type header = unit {
    osize : uint8;
    seq : uint8;

    on %done {
      self.size = self.osize;
      self.size = self.size << 8;
      self.size = self.size + self.osize;
      self.size = self.size << 8;
      self.size = self.size + self.osize;

    }
    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) 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;    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:spicy是zeek用于界说协议解析的语言,可参考https://zeek.org ↩︎

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Mysql Resultset 解析记录