CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rbmysql/protocol.rb
Views: 11766
1
# coding: ascii-8bit
2
# Copyright (C) 2008-2012 TOMITA Masahiro
3
# mailto:[email protected]
4
5
require "socket"
6
require "timeout"
7
require "digest/sha1"
8
require "stringio"
9
10
class RbMysql
11
# MySQL network protocol
12
class Protocol
13
14
VERSION = 10
15
MAX_PACKET_LENGTH = 2**24-1
16
17
# Convert netdata to Ruby value
18
# === Argument
19
# data :: [Packet] packet data
20
# type :: [Integer] field type
21
# unsigned :: [true or false] true if value is unsigned
22
# === Return
23
# Object :: converted value.
24
def self.net2value(pkt, type, unsigned)
25
case type
26
when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB, Field::TYPE_JSON
27
return pkt.lcs
28
when Field::TYPE_TINY
29
v = pkt.utiny
30
return unsigned ? v : v < 128 ? v : v-256
31
when Field::TYPE_SHORT
32
v = pkt.ushort
33
return unsigned ? v : v < 32768 ? v : v-65536
34
when Field::TYPE_INT24, Field::TYPE_LONG
35
v = pkt.ulong
36
return unsigned ? v : v < 0x8000_0000 ? v : v-0x10000_0000
37
when Field::TYPE_LONGLONG
38
n1, n2 = pkt.ulong, pkt.ulong
39
v = (n2 << 32) | n1
40
return unsigned ? v : v < 0x8000_0000_0000_0000 ? v : v-0x10000_0000_0000_0000
41
when Field::TYPE_FLOAT
42
return pkt.read(4).unpack('e').first
43
when Field::TYPE_DOUBLE
44
return pkt.read(8).unpack('E').first
45
when Field::TYPE_DATE
46
len = pkt.utiny
47
y, m, d = pkt.read(len).unpack("vCC")
48
t = RbMysql::Time.new(y, m, d, nil, nil, nil)
49
return t
50
when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
51
len = pkt.utiny
52
y, m, d, h, mi, s, sp = pkt.read(len).unpack("vCCCCCV")
53
return RbMysql::Time.new(y, m, d, h, mi, s, false, sp)
54
when Field::TYPE_TIME
55
len = pkt.utiny
56
sign, d, h, mi, s, sp = pkt.read(len).unpack("CVCCCV")
57
h = d.to_i * 24 + h.to_i
58
return RbMysql::Time.new(0, 0, 0, h, mi, s, sign!=0, sp)
59
when Field::TYPE_YEAR
60
return pkt.ushort
61
when Field::TYPE_BIT
62
return pkt.lcs
63
else
64
raise "not implemented: type=#{type}"
65
end
66
end
67
68
# convert Ruby value to netdata
69
# === Argument
70
# v :: [Object] Ruby value.
71
# === Return
72
# Integer :: type of column. Field::TYPE_*
73
# String :: netdata
74
# === Exception
75
# ProtocolError :: value too large / value is not supported
76
def self.value2net(v)
77
case v
78
when nil
79
type = Field::TYPE_NULL
80
val = ""
81
when Integer
82
if -0x8000_0000 <= v && v < 0x8000_0000
83
type = Field::TYPE_LONG
84
val = [v].pack('V')
85
elsif -0x8000_0000_0000_0000 <= v && v < 0x8000_0000_0000_0000
86
type = Field::TYPE_LONGLONG
87
val = [v&0xffffffff, v>>32].pack("VV")
88
elsif 0x8000_0000_0000_0000 <= v && v <= 0xffff_ffff_ffff_ffff
89
type = Field::TYPE_LONGLONG | 0x8000
90
val = [v&0xffffffff, v>>32].pack("VV")
91
else
92
raise ProtocolError, "value too large: #{v}"
93
end
94
when Float
95
type = Field::TYPE_DOUBLE
96
val = [v].pack("E")
97
when String
98
type = Field::TYPE_STRING
99
val = Packet.lcs(v)
100
when ::Time
101
type = Field::TYPE_DATETIME
102
val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, v.usec].pack("CvCCCCCV")
103
when RbMysql::Time
104
type = Field::TYPE_DATETIME
105
val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, v.second_part].pack("CvCCCCCV")
106
else
107
raise ProtocolError, "class #{v.class} is not supported"
108
end
109
return type, val
110
end
111
112
attr_reader :server_info
113
attr_reader :server_version
114
attr_reader :thread_id
115
attr_reader :sqlstate
116
attr_reader :affected_rows
117
attr_reader :insert_id
118
attr_reader :server_status
119
attr_reader :warning_count
120
attr_reader :message
121
attr_accessor :charset
122
123
# @state variable keep state for connection.
124
# :INIT :: Initial state.
125
# :READY :: Ready for command.
126
# :FIELD :: After query(). retr_fields() is needed.
127
# :RESULT :: After retr_fields(), retr_all_records() or stmt_retr_all_records() is needed.
128
129
# make socket connection to server.
130
# === Argument
131
# host :: [String] if "localhost" or "" nil then use UNIXSocket. Otherwise use TCPSocket
132
# port :: [Integer] port number using by TCPSocket
133
# socket :: [String,Socket] socket file name using by UNIXSocket, or an existing ::Socket instance
134
# conn_timeout :: [Integer] connect timeout (sec).
135
# read_timeout :: [Integer] read timeout (sec).
136
# write_timeout :: [Integer] write timeout (sec).
137
# === Exception
138
# [ClientError] :: connection timeout
139
def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout)
140
@insert_id = 0
141
@warning_count = 0
142
@gc_stmt_queue = [] # stmt id list which GC destroy.
143
set_state :INIT
144
@read_timeout = read_timeout
145
@write_timeout = write_timeout
146
begin
147
Timeout.timeout conn_timeout do
148
if host.nil? or host.empty? or host == "localhost"
149
socket ||= ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
150
@sock = UNIXSocket.new socket
151
else
152
if !socket
153
port ||= ENV["MYSQL_TCP_PORT"] || (Socket.getservbyname("mysql","tcp") rescue MYSQL_TCP_PORT)
154
@sock = TCPSocket.new host, port
155
else
156
@sock = socket
157
end
158
end
159
end
160
rescue Timeout::Error
161
raise ClientError, "connection timeout"
162
end
163
end
164
165
def close
166
@sock.close
167
end
168
169
# initial negotiate and authenticate.
170
# === Argument
171
# user :: [String / nil] username
172
# passwd :: [String / nil] password
173
# db :: [String / nil] default database name. nil: no default.
174
# flag :: [Integer] client flag
175
# charset :: [RbMysql::Charset / nil] charset for connection. nil: use server's charset
176
# === Exception
177
# ProtocolError :: The old style password is not supported
178
def authenticate(user, passwd, db, flag, charset)
179
check_state :INIT
180
@authinfo = [user, passwd, db, flag, charset]
181
reset
182
init_packet = InitialPacket.parse read
183
@server_info = init_packet.server_version
184
@server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
185
@thread_id = init_packet.thread_id
186
@client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
187
@client_flags |= CLIENT_CONNECT_WITH_DB if db
188
@client_flags |= flag
189
@charset = charset
190
unless @charset
191
@charset = Charset.by_number(init_packet.server_charset)
192
@charset.encoding # raise error if unsupported charset
193
end
194
netpw = encrypt_password passwd, init_packet.scramble_buff
195
write AuthenticationPacket.serialize(@client_flags, 1024**3, @charset.number, user, netpw, db)
196
raise ProtocolError, 'The old style password is not supported' if read.to_s == "\xfe"
197
set_state :READY
198
end
199
200
# Quit command
201
def quit_command
202
synchronize do
203
reset
204
write [COM_QUIT].pack("C")
205
close
206
end
207
end
208
209
# Query command
210
# === Argument
211
# query :: [String] query string
212
# === Return
213
# [Integer / nil] number of fields of results. nil if no results.
214
def query_command(query)
215
check_state :READY
216
begin
217
reset
218
write [COM_QUERY, @charset.convert(query)].pack("Ca*")
219
get_result
220
rescue
221
set_state :READY
222
raise
223
end
224
end
225
226
# get result of query.
227
# === Return
228
# [integer / nil] number of fields of results. nil if no results.
229
def get_result
230
begin
231
res_packet = ResultPacket.parse read
232
if res_packet.field_count.to_i > 0 # result data exists
233
set_state :FIELD
234
return res_packet.field_count
235
end
236
if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
237
if @client_flags.to_i & CLIENT_LOCAL_FILES == 0
238
raise ProtocolError, 'Load data local infile forbidden'
239
end
240
filename = res_packet.message
241
File.open(filename){|f| write f}
242
write nil # EOF mark
243
read
244
end
245
@affected_rows, @insert_id, @server_status, @warning_count, @message =
246
res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
247
set_state :READY
248
return nil
249
rescue
250
set_state :READY
251
raise
252
end
253
end
254
255
# Retrieve n fields
256
# === Argument
257
# n :: [Integer] number of fields
258
# === Return
259
# [Array of RbMysql::Field] field list
260
def retr_fields(n)
261
check_state :FIELD
262
begin
263
fields = n.times.map{Field.new FieldPacket.parse(read)}
264
read_eof_packet
265
set_state :RESULT
266
fields
267
rescue
268
set_state :READY
269
raise
270
end
271
end
272
273
# Retrieve all records for simple query
274
# === Argument
275
# fields :: [Array<RbMysql::Field>] number of fields
276
# === Return
277
# [Array of Array of String] all records
278
def retr_all_records(fields)
279
check_state :RESULT
280
enc = charset.encoding
281
begin
282
all_recs = []
283
until (pkt = read).eof?
284
all_recs.push RawRecord.new(pkt, fields, enc)
285
end
286
pkt.read(3)
287
@server_status = pkt.utiny
288
all_recs
289
ensure
290
set_state :READY
291
end
292
end
293
294
# Field list command
295
# === Argument
296
# table :: [String] table name.
297
# field :: [String / nil] field name that may contain wild card.
298
# === Return
299
# [Array of Field] field list
300
def field_list_command(table, field)
301
synchronize do
302
reset
303
write [COM_FIELD_LIST, table, 0, field].pack("Ca*Ca*")
304
fields = []
305
until (data = read).eof?
306
fields.push Field.new(FieldPacket.parse(data))
307
end
308
return fields
309
end
310
end
311
312
# Process info command
313
# === Return
314
# [Array of Field] field list
315
def process_info_command
316
check_state :READY
317
begin
318
reset
319
write [COM_PROCESS_INFO].pack("C")
320
field_count = read.lcb
321
fields = field_count.times.map{Field.new FieldPacket.parse(read)}
322
read_eof_packet
323
set_state :RESULT
324
return fields
325
rescue
326
set_state :READY
327
raise
328
end
329
end
330
331
# Ping command
332
def ping_command
333
simple_command [COM_PING].pack("C")
334
end
335
336
# Kill command
337
def kill_command(pid)
338
simple_command [COM_PROCESS_KILL, pid].pack("CV")
339
end
340
341
# Refresh command
342
def refresh_command(op)
343
simple_command [COM_REFRESH, op].pack("CC")
344
end
345
346
# Set option command
347
def set_option_command(opt)
348
simple_command [COM_SET_OPTION, opt].pack("Cv")
349
end
350
351
# Shutdown command
352
def shutdown_command(level)
353
simple_command [COM_SHUTDOWN, level].pack("CC")
354
end
355
356
# Statistics command
357
def statistics_command
358
simple_command [COM_STATISTICS].pack("C")
359
end
360
361
# Stmt prepare command
362
# === Argument
363
# stmt :: [String] prepared statement
364
# === Return
365
# [Integer] statement id
366
# [Integer] number of parameters
367
# [Array of Field] field list
368
def stmt_prepare_command(stmt)
369
synchronize do
370
reset
371
write [COM_STMT_PREPARE, charset.convert(stmt)].pack("Ca*")
372
res_packet = PrepareResultPacket.parse read
373
if res_packet.param_count > 0
374
res_packet.param_count.times{read} # skip parameter packet
375
read_eof_packet
376
end
377
if res_packet.field_count > 0
378
fields = res_packet.field_count.times.map{Field.new FieldPacket.parse(read)}
379
read_eof_packet
380
else
381
fields = []
382
end
383
return res_packet.statement_id, res_packet.param_count, fields
384
end
385
end
386
387
# Stmt execute command
388
# === Argument
389
# stmt_id :: [Integer] statement id
390
# values :: [Array] parameters
391
# === Return
392
# [Integer] number of fields
393
def stmt_execute_command(stmt_id, values)
394
check_state :READY
395
begin
396
reset
397
write ExecutePacket.serialize(stmt_id, RbMysql::Stmt::CURSOR_TYPE_NO_CURSOR, values)
398
get_result
399
rescue
400
set_state :READY
401
raise
402
end
403
end
404
405
# Retrieve all records for prepared statement
406
# === Argument
407
# fields :: [Array of RbMysql::Fields] field list
408
# charset :: [RbMysql::Charset]
409
# === Return
410
# [Array of Array of Object] all records
411
def stmt_retr_all_records(fields, charset)
412
check_state :RESULT
413
enc = charset.encoding
414
begin
415
all_recs = []
416
until (pkt = read).eof?
417
all_recs.push StmtRawRecord.new(pkt, fields, enc)
418
end
419
all_recs
420
ensure
421
set_state :READY
422
end
423
end
424
425
# Stmt close command
426
# === Argument
427
# stmt_id :: [Integer] statement id
428
def stmt_close_command(stmt_id)
429
synchronize do
430
reset
431
write [COM_STMT_CLOSE, stmt_id].pack("CV")
432
end
433
end
434
435
def gc_stmt(stmt_id)
436
@gc_stmt_queue.push stmt_id
437
end
438
439
private
440
441
def check_state(st)
442
raise 'command out of sync' unless @state == st
443
end
444
445
def set_state(st)
446
@state = st
447
if st == :READY
448
gc_disabled = GC.disable
449
begin
450
while st = @gc_stmt_queue.shift
451
reset
452
write [COM_STMT_CLOSE, st].pack("CV")
453
end
454
ensure
455
GC.enable unless gc_disabled
456
end
457
end
458
end
459
460
def synchronize
461
begin
462
check_state :READY
463
return yield
464
ensure
465
set_state :READY
466
end
467
end
468
469
# Reset sequence number
470
def reset
471
@seq = 0 # packet counter. reset by each command
472
end
473
474
# Read one packet data
475
# === Return
476
# [Packet] packet data
477
# === Exception
478
# [ProtocolError] invalid packet sequence number
479
def read
480
data = ''
481
len = nil
482
begin
483
Timeout.timeout @read_timeout do
484
header = @sock.read(4)
485
raise EOFError unless header && header.length == 4
486
len1, len2, seq = header.unpack("CvC")
487
len = (len2 << 8) + len1
488
raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
489
@seq = (@seq + 1) % 256
490
ret = @sock.read(len)
491
raise EOFError unless ret && ret.length == len
492
data.concat ret
493
end
494
rescue EOFError
495
raise ClientError::ServerGoneError, 'MySQL server has gone away'
496
rescue Timeout::Error
497
raise ClientError, "read timeout"
498
end while len == MAX_PACKET_LENGTH
499
500
@sqlstate = "00000"
501
502
# Error packet
503
if data[0] == ?\xff
504
f, errno, marker, @sqlstate, message = data.unpack("Cvaa5a*")
505
unless marker == "#"
506
f, errno, message = data.unpack("Cva*") # Version 4.0 Error
507
@sqlstate = ""
508
end
509
message.force_encoding(@charset.encoding) if @charset
510
if RbMysql::ServerError::ERROR_MAP.key? errno
511
raise RbMysql::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
512
end
513
raise RbMysql::ServerError.new(message, @sqlstate)
514
end
515
Packet.new(data)
516
end
517
518
# Write one packet data
519
# === Argument
520
# data :: [String / IO] packet data. If data is nil, write empty packet.
521
def write(data)
522
begin
523
@sock.sync = false
524
if data.nil?
525
Timeout.timeout @write_timeout do
526
@sock.write [0, 0, @seq].pack("CvC")
527
end
528
@seq = (@seq + 1) % 256
529
else
530
data = StringIO.new data if data.is_a? String
531
while d = data.read(MAX_PACKET_LENGTH)
532
Timeout.timeout @write_timeout do
533
@sock.write [d.length%256, d.length/256, @seq].pack("CvC")
534
@sock.write d
535
end
536
@seq = (@seq + 1) % 256
537
end
538
end
539
@sock.sync = true
540
Timeout.timeout @write_timeout do
541
@sock.flush
542
end
543
rescue Errno::EPIPE
544
raise ClientError::ServerGoneError, 'MySQL server has gone away'
545
rescue Timeout::Error
546
raise ClientError, "write timeout"
547
end
548
end
549
550
# Read EOF packet
551
# === Exception
552
# [ProtocolError] packet is not EOF
553
def read_eof_packet
554
raise ProtocolError, "packet is not EOF" unless read.eof?
555
end
556
557
# Send simple command
558
# === Argument
559
# packet :: [String] packet data
560
# === Return
561
# [String] received data
562
def simple_command(packet)
563
synchronize do
564
reset
565
write packet
566
read.to_s
567
end
568
end
569
570
# Encrypt password
571
# === Argument
572
# plain :: [String] plain password.
573
# scramble :: [String] scramble code from initial packet.
574
# === Return
575
# [String] encrypted password
576
def encrypt_password(plain, scramble)
577
return "" if plain.nil? or plain.empty?
578
hash_stage1 = Digest::SHA1.digest plain
579
hash_stage2 = Digest::SHA1.digest hash_stage1
580
return hash_stage1.unpack("C*").zip(Digest::SHA1.digest(scramble+hash_stage2).unpack("C*")).map{|a,b| a^b}.pack("C*")
581
end
582
583
# Initial packet
584
class InitialPacket
585
def self.parse(pkt)
586
protocol_version = pkt.utiny
587
server_version = pkt.string
588
thread_id = pkt.ulong
589
scramble_buff = pkt.read(8)
590
f0 = pkt.utiny
591
server_capabilities = pkt.ushort
592
server_charset = pkt.utiny
593
server_status = pkt.ushort
594
_f1 = pkt.read(13)
595
rest_scramble_buff = pkt.string
596
raise ProtocolError, "unsupported version: #{protocol_version}" unless protocol_version == VERSION
597
raise ProtocolError, "invalid packet: f0=#{f0}" unless f0 == 0
598
scramble_buff.concat rest_scramble_buff
599
self.new protocol_version, server_version, thread_id, server_capabilities, server_charset, server_status, scramble_buff
600
end
601
602
attr_reader :protocol_version, :server_version, :thread_id, :server_capabilities, :server_charset, :server_status, :scramble_buff
603
604
def initialize(*args)
605
@protocol_version, @server_version, @thread_id, @server_capabilities, @server_charset, @server_status, @scramble_buff = args
606
end
607
end
608
609
# Result packet
610
class ResultPacket
611
def self.parse(pkt)
612
field_count = pkt.lcb
613
if field_count == 0
614
affected_rows = pkt.lcb
615
insert_id = pkt.lcb
616
server_status = pkt.ushort
617
warning_count = pkt.ushort
618
message = pkt.lcs
619
return self.new(field_count, affected_rows, insert_id, server_status, warning_count, message)
620
elsif field_count.nil? # LOAD DATA LOCAL INFILE
621
return self.new(nil, nil, nil, nil, nil, pkt.to_s)
622
else
623
return self.new(field_count)
624
end
625
end
626
627
attr_reader :field_count, :affected_rows, :insert_id, :server_status, :warning_count, :message
628
629
def initialize(*args)
630
@field_count, @affected_rows, @insert_id, @server_status, @warning_count, @message = args
631
end
632
end
633
634
# Field packet
635
class FieldPacket
636
def self.parse(pkt)
637
_first = pkt.lcs
638
db = pkt.lcs
639
table = pkt.lcs
640
org_table = pkt.lcs
641
name = pkt.lcs
642
org_name = pkt.lcs
643
_f0 = pkt.utiny
644
charsetnr = pkt.ushort
645
length = pkt.ulong
646
type = pkt.utiny
647
flags = pkt.ushort
648
decimals = pkt.utiny
649
f1 = pkt.ushort
650
651
raise ProtocolError, "invalid packet: f1=#{f1}" unless f1 == 0
652
default = pkt.lcs
653
return self.new(db, table, org_table, name, org_name, charsetnr, length, type, flags, decimals, default)
654
end
655
656
attr_reader :db, :table, :org_table, :name, :org_name, :charsetnr, :length, :type, :flags, :decimals, :default
657
658
def initialize(*args)
659
@db, @table, @org_table, @name, @org_name, @charsetnr, @length, @type, @flags, @decimals, @default = args
660
end
661
end
662
663
# Prepare result packet
664
class PrepareResultPacket
665
def self.parse(pkt)
666
raise ProtocolError, "invalid packet" unless pkt.utiny == 0
667
statement_id = pkt.ulong
668
field_count = pkt.ushort
669
param_count = pkt.ushort
670
f = pkt.utiny
671
warning_count = pkt.ushort
672
raise ProtocolError, "invalid packet" unless f == 0x00
673
self.new statement_id, field_count, param_count, warning_count
674
end
675
676
attr_reader :statement_id, :field_count, :param_count, :warning_count
677
678
def initialize(*args)
679
@statement_id, @field_count, @param_count, @warning_count = args
680
end
681
end
682
683
# Authentication packet
684
class AuthenticationPacket
685
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename)
686
[
687
client_flags,
688
max_packet_size,
689
Packet.lcb(charset_number),
690
"", # always 0x00 * 23
691
username,
692
Packet.lcs(scrambled_password),
693
databasename
694
].pack("VVa*a23Z*A*Z*")
695
end
696
end
697
698
# Execute packet
699
class ExecutePacket
700
def self.serialize(statement_id, cursor_type, values)
701
nbm = null_bitmap values
702
netvalues = ""
703
types = values.map do |v|
704
t, n = Protocol.value2net v
705
netvalues.concat n if v
706
t
707
end
708
[RbMysql::COM_STMT_EXECUTE, statement_id, cursor_type, 1, nbm, 1, types.pack("v*"), netvalues].pack("CVCVa*Ca*a*")
709
end
710
711
# make null bitmap
712
#
713
# If values is [1, nil, 2, 3, nil] then returns "\x12"(0b10010).
714
def self.null_bitmap(values)
715
bitmap = values.enum_for(:each_slice,8).map do |vals|
716
vals.reverse.inject(0){|b, v|(b << 1 | (v ? 0 : 1))}
717
end
718
return bitmap.pack("C*")
719
end
720
721
end
722
end
723
724
class RawRecord
725
def initialize(packet, fields, encoding)
726
@packet, @fields, @encoding = packet, fields, encoding
727
end
728
729
def to_a
730
@fields.map do |f|
731
if s = @packet.lcs
732
unless f.type == Field::TYPE_BIT or f.charsetnr == Charset::BINARY_CHARSET_NUMBER
733
s = Charset.convert_encoding(s, @encoding)
734
end
735
end
736
s
737
end
738
end
739
end
740
741
class StmtRawRecord
742
# === Argument
743
# pkt :: [Packet]
744
# fields :: [Array of Fields]
745
# encoding:: [Encoding]
746
def initialize(packet, fields, encoding)
747
@packet, @fields, @encoding = packet, fields, encoding
748
end
749
750
# Parse statement result packet
751
# === Return
752
# [Array of Object] one record
753
def parse_record_packet
754
@packet.utiny # skip first byte
755
null_bit_map = @packet.read((@fields.length+7+2)/8).unpack("b*").first
756
rec = @fields.each_with_index.map do |f, i|
757
if null_bit_map[i+2] == ?1
758
nil
759
else
760
unsigned = f.flags & Field::UNSIGNED_FLAG != 0
761
v = Protocol.net2value(@packet, f.type, unsigned)
762
if v.is_a? Numeric or v.is_a? RbMysql::Time
763
v
764
elsif f.type == Field::TYPE_BIT or f.charsetnr == Charset::BINARY_CHARSET_NUMBER
765
Charset.to_binary(v)
766
else
767
Charset.convert_encoding(v, @encoding)
768
end
769
end
770
end
771
rec
772
end
773
774
alias to_a parse_record_packet
775
776
end
777
end
778
779