~percona-toolkit-dev/percona-toolkit/fix-change-master-bug-932614

« back to all changes in this revision

Viewing changes to lib/MySQLProtocolParser.pm

  • Committer: Daniel Nichter
  • Date: 2011-06-24 17:22:06 UTC
  • Revision ID: daniel@percona.com-20110624172206-c7q4s4ad6r260zz6
Add lib/, t/lib/, and sandbox/.  All modules are updated and passing on MySQL 5.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This program is copyright 2007-2011 Percona Inc.
 
2
# Feedback and improvements are welcome.
 
3
#
 
4
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
5
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
6
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
7
#
 
8
# This program is free software; you can redistribute it and/or modify it under
 
9
# the terms of the GNU General Public License as published by the Free Software
 
10
# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
11
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
12
# licenses.
 
13
#
 
14
# You should have received a copy of the GNU General Public License along with
 
15
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
16
# Place, Suite 330, Boston, MA  02111-1307  USA.
 
17
# ###########################################################################
 
18
# MySQLProtocolParser package $Revision: 7522 $
 
19
# ###########################################################################
 
20
package MySQLProtocolParser;
 
21
 
 
22
# This creates events suitable for mk-query-digest from raw MySQL packets.
 
23
# The packets come from TcpdumpParser.  MySQLProtocolParse::parse_packet()
 
24
# should be first in the callback chain because it creates events for
 
25
# subsequent callbacks.  So the sequence is:
 
26
#    1. mk-query-digest calls TcpdumpParser::parse_event($fh, ..., @callbacks)
 
27
#    2. TcpdumpParser::parse_event() extracts raw MySQL packets from $fh and
 
28
#       passes them to the callbacks, the first of which is
 
29
#       MySQLProtocolParser::parse_packet().
 
30
#    3. MySQLProtocolParser::parse_packet() makes events from the packets
 
31
#       and returns them to TcpdumpParser::parse_event().
 
32
#    4. TcpdumpParser::parse_event() passes the newly created events to
 
33
#       the subsequent callbacks.
 
34
# At times MySQLProtocolParser::parse_packet() will not return an event
 
35
# because it usually takes a few packets to create one event.  In such
 
36
# cases, TcpdumpParser::parse_event() will not call the other callbacks.
 
37
 
 
38
use strict;
 
39
use warnings FATAL => 'all';
 
40
use English qw(-no_match_vars);
 
41
 
 
42
eval {
 
43
   require IO::Uncompress::Inflate;
 
44
   IO::Uncompress::Inflate->import(qw(inflate $InflateError));
 
45
};
 
46
 
 
47
use Data::Dumper;
 
48
$Data::Dumper::Indent    = 1;
 
49
$Data::Dumper::Sortkeys  = 1;
 
50
$Data::Dumper::Quotekeys = 0;
 
51
 
 
52
require Exporter;
 
53
our @ISA         = qw(Exporter);
 
54
our %EXPORT_TAGS = ();
 
55
our @EXPORT      = ();
 
56
our @EXPORT_OK   = qw(
 
57
   parse_error_packet
 
58
   parse_ok_packet
 
59
   parse_ok_prepared_statement_packet
 
60
   parse_server_handshake_packet
 
61
   parse_client_handshake_packet
 
62
   parse_com_packet
 
63
   parse_flags
 
64
);
 
65
 
 
66
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
67
use constant {
 
68
   COM_SLEEP               => '00',
 
69
   COM_QUIT                => '01',
 
70
   COM_INIT_DB             => '02',
 
71
   COM_QUERY               => '03',
 
72
   COM_FIELD_LIST          => '04',
 
73
   COM_CREATE_DB           => '05',
 
74
   COM_DROP_DB             => '06',
 
75
   COM_REFRESH             => '07',
 
76
   COM_SHUTDOWN            => '08',
 
77
   COM_STATISTICS          => '09',
 
78
   COM_PROCESS_INFO        => '0a',
 
79
   COM_CONNECT             => '0b',
 
80
   COM_PROCESS_KILL        => '0c',
 
81
   COM_DEBUG               => '0d',
 
82
   COM_PING                => '0e',
 
83
   COM_TIME                => '0f',
 
84
   COM_DELAYED_INSERT      => '10',
 
85
   COM_CHANGE_USER         => '11',
 
86
   COM_BINLOG_DUMP         => '12',
 
87
   COM_TABLE_DUMP          => '13',
 
88
   COM_CONNECT_OUT         => '14',
 
89
   COM_REGISTER_SLAVE      => '15',
 
90
   COM_STMT_PREPARE        => '16',
 
91
   COM_STMT_EXECUTE        => '17',
 
92
   COM_STMT_SEND_LONG_DATA => '18',
 
93
   COM_STMT_CLOSE          => '19',
 
94
   COM_STMT_RESET          => '1a',
 
95
   COM_SET_OPTION          => '1b',
 
96
   COM_STMT_FETCH          => '1c',
 
97
   SERVER_QUERY_NO_GOOD_INDEX_USED => 16,
 
98
   SERVER_QUERY_NO_INDEX_USED      => 32,
 
99
};
 
100
 
 
101
my %com_for = (
 
102
   '00' => 'COM_SLEEP',
 
103
   '01' => 'COM_QUIT',
 
104
   '02' => 'COM_INIT_DB',
 
105
   '03' => 'COM_QUERY',
 
106
   '04' => 'COM_FIELD_LIST',
 
107
   '05' => 'COM_CREATE_DB',
 
108
   '06' => 'COM_DROP_DB',
 
109
   '07' => 'COM_REFRESH',
 
110
   '08' => 'COM_SHUTDOWN',
 
111
   '09' => 'COM_STATISTICS',
 
112
   '0a' => 'COM_PROCESS_INFO',
 
113
   '0b' => 'COM_CONNECT',
 
114
   '0c' => 'COM_PROCESS_KILL',
 
115
   '0d' => 'COM_DEBUG',
 
116
   '0e' => 'COM_PING',
 
117
   '0f' => 'COM_TIME',
 
118
   '10' => 'COM_DELAYED_INSERT',
 
119
   '11' => 'COM_CHANGE_USER',
 
120
   '12' => 'COM_BINLOG_DUMP',
 
121
   '13' => 'COM_TABLE_DUMP',
 
122
   '14' => 'COM_CONNECT_OUT',
 
123
   '15' => 'COM_REGISTER_SLAVE',
 
124
   '16' => 'COM_STMT_PREPARE',
 
125
   '17' => 'COM_STMT_EXECUTE',
 
126
   '18' => 'COM_STMT_SEND_LONG_DATA',
 
127
   '19' => 'COM_STMT_CLOSE',
 
128
   '1a' => 'COM_STMT_RESET',
 
129
   '1b' => 'COM_SET_OPTION',
 
130
   '1c' => 'COM_STMT_FETCH',
 
131
);
 
132
 
 
133
my %flag_for = (
 
134
   'CLIENT_LONG_PASSWORD'     => 1,       # new more secure passwords 
 
135
   'CLIENT_FOUND_ROWS'        => 2,       # Found instead of affected rows 
 
136
   'CLIENT_LONG_FLAG'         => 4,       # Get all column flags 
 
137
   'CLIENT_CONNECT_WITH_DB'   => 8,       # One can specify db on connect 
 
138
   'CLIENT_NO_SCHEMA'         => 16,      # Don't allow database.table.column 
 
139
   'CLIENT_COMPRESS'          => 32,      # Can use compression protocol 
 
140
   'CLIENT_ODBC'              => 64,      # Odbc client 
 
141
   'CLIENT_LOCAL_FILES'       => 128,     # Can use LOAD DATA LOCAL 
 
142
   'CLIENT_IGNORE_SPACE'      => 256,     # Ignore spaces before '(' 
 
143
   'CLIENT_PROTOCOL_41'       => 512,     # New 4.1 protocol 
 
144
   'CLIENT_INTERACTIVE'       => 1024,    # This is an interactive client 
 
145
   'CLIENT_SSL'               => 2048,    # Switch to SSL after handshake 
 
146
   'CLIENT_IGNORE_SIGPIPE'    => 4096,    # IGNORE sigpipes 
 
147
   'CLIENT_TRANSACTIONS'      => 8192,    # Client knows about transactions 
 
148
   'CLIENT_RESERVED'          => 16384,   # Old flag for 4.1 protocol  
 
149
   'CLIENT_SECURE_CONNECTION' => 32768,   # New 4.1 authentication 
 
150
   'CLIENT_MULTI_STATEMENTS'  => 65536,   # Enable/disable multi-stmt support 
 
151
   'CLIENT_MULTI_RESULTS'     => 131072,  # Enable/disable multi-results 
 
152
);
 
153
 
 
154
use constant {
 
155
   MYSQL_TYPE_DECIMAL      => 0,
 
156
   MYSQL_TYPE_TINY         => 1,
 
157
   MYSQL_TYPE_SHORT        => 2,
 
158
   MYSQL_TYPE_LONG         => 3,
 
159
   MYSQL_TYPE_FLOAT        => 4,
 
160
   MYSQL_TYPE_DOUBLE       => 5,
 
161
   MYSQL_TYPE_NULL         => 6,
 
162
   MYSQL_TYPE_TIMESTAMP    => 7,
 
163
   MYSQL_TYPE_LONGLONG     => 8,
 
164
   MYSQL_TYPE_INT24        => 9,
 
165
   MYSQL_TYPE_DATE         => 10,
 
166
   MYSQL_TYPE_TIME         => 11,
 
167
   MYSQL_TYPE_DATETIME     => 12,
 
168
   MYSQL_TYPE_YEAR         => 13,
 
169
   MYSQL_TYPE_NEWDATE      => 14,
 
170
   MYSQL_TYPE_VARCHAR      => 15,
 
171
   MYSQL_TYPE_BIT          => 16,
 
172
   MYSQL_TYPE_NEWDECIMAL   => 246,
 
173
   MYSQL_TYPE_ENUM         => 247,
 
174
   MYSQL_TYPE_SET          => 248,
 
175
   MYSQL_TYPE_TINY_BLOB    => 249,
 
176
   MYSQL_TYPE_MEDIUM_BLOB  => 250,
 
177
   MYSQL_TYPE_LONG_BLOB    => 251,
 
178
   MYSQL_TYPE_BLOB         => 252,
 
179
   MYSQL_TYPE_VAR_STRING   => 253,
 
180
   MYSQL_TYPE_STRING       => 254,
 
181
   MYSQL_TYPE_GEOMETRY     => 255,
 
182
};
 
183
 
 
184
my %type_for = (
 
185
   0   => 'MYSQL_TYPE_DECIMAL',
 
186
   1   => 'MYSQL_TYPE_TINY',
 
187
   2   => 'MYSQL_TYPE_SHORT',
 
188
   3   => 'MYSQL_TYPE_LONG',
 
189
   4   => 'MYSQL_TYPE_FLOAT',
 
190
   5   => 'MYSQL_TYPE_DOUBLE',
 
191
   6   => 'MYSQL_TYPE_NULL',
 
192
   7   => 'MYSQL_TYPE_TIMESTAMP',
 
193
   8   => 'MYSQL_TYPE_LONGLONG',
 
194
   9   => 'MYSQL_TYPE_INT24',
 
195
   10  => 'MYSQL_TYPE_DATE',
 
196
   11  => 'MYSQL_TYPE_TIME',
 
197
   12  => 'MYSQL_TYPE_DATETIME',
 
198
   13  => 'MYSQL_TYPE_YEAR',
 
199
   14  => 'MYSQL_TYPE_NEWDATE',
 
200
   15  => 'MYSQL_TYPE_VARCHAR',
 
201
   16  => 'MYSQL_TYPE_BIT',
 
202
   246 => 'MYSQL_TYPE_NEWDECIMAL',
 
203
   247 => 'MYSQL_TYPE_ENUM',
 
204
   248 => 'MYSQL_TYPE_SET',
 
205
   249 => 'MYSQL_TYPE_TINY_BLOB',
 
206
   250 => 'MYSQL_TYPE_MEDIUM_BLOB',
 
207
   251 => 'MYSQL_TYPE_LONG_BLOB',
 
208
   252 => 'MYSQL_TYPE_BLOB',
 
209
   253 => 'MYSQL_TYPE_VAR_STRING',
 
210
   254 => 'MYSQL_TYPE_STRING',
 
211
   255 => 'MYSQL_TYPE_GEOMETRY',
 
212
);
 
213
 
 
214
my %unpack_type = (
 
215
   MYSQL_TYPE_NULL       => sub { return 'NULL', 0; },
 
216
   MYSQL_TYPE_TINY       => sub { return to_num(@_, 1), 1; },
 
217
   MySQL_TYPE_SHORT      => sub { return to_num(@_, 2), 2; },
 
218
   MYSQL_TYPE_LONG       => sub { return to_num(@_, 4), 4; },
 
219
   MYSQL_TYPE_LONGLONG   => sub { return to_num(@_, 8), 8; },
 
220
   MYSQL_TYPE_DOUBLE     => sub { return to_double(@_), 8; },
 
221
   MYSQL_TYPE_VARCHAR    => \&unpack_string,
 
222
   MYSQL_TYPE_VAR_STRING => \&unpack_string,
 
223
   MYSQL_TYPE_STRING     => \&unpack_string,
 
224
);
 
225
 
 
226
# server is the "host:port" of the sever being watched.  It's auto-guessed if
 
227
# not specified.  version is a placeholder for handling differences between
 
228
# MySQL v4.0 and older and v4.1 and newer.  Currently, we only handle v4.1.
 
229
sub new {
 
230
   my ( $class, %args ) = @_;
 
231
 
 
232
   my $self = {
 
233
      server         => $args{server},
 
234
      port           => $args{port} || '3306',
 
235
      version        => '41',    # MySQL proto version; not used yet
 
236
      sessions       => {},
 
237
      o              => $args{o},
 
238
      fake_thread_id => 2**32,   # see _make_event()
 
239
   };
 
240
   MKDEBUG && $self->{server} && _d('Watching only server', $self->{server});
 
241
   return bless $self, $class;
 
242
}
 
243
 
 
244
# The packet arg should be a hashref from TcpdumpParser::parse_event().
 
245
# misc is a placeholder for future features.
 
246
sub parse_event {
 
247
   my ( $self, %args ) = @_;
 
248
   my @required_args = qw(event);
 
249
   foreach my $arg ( @required_args ) {
 
250
      die "I need a $arg argument" unless $args{$arg};
 
251
   }
 
252
   my $packet = @args{@required_args};
 
253
 
 
254
   my $src_host = "$packet->{src_host}:$packet->{src_port}";
 
255
   my $dst_host = "$packet->{dst_host}:$packet->{dst_port}";
 
256
 
 
257
   if ( my $server = $self->{server} ) {  # Watch only the given server.
 
258
      $server .= ":$self->{port}";
 
259
      if ( $src_host ne $server && $dst_host ne $server ) {
 
260
         MKDEBUG && _d('Packet is not to or from', $server);
 
261
         return;
 
262
      }
 
263
   }
 
264
 
 
265
   # Auto-detect the server by looking for port 3306 or port "mysql" (sometimes
 
266
   # tcpdump will substitute the port by a lookup in /etc/protocols).
 
267
   my $packet_from;
 
268
   my $client;
 
269
   if ( $src_host =~ m/:$self->{port}$/ ) {
 
270
      $packet_from = 'server';
 
271
      $client      = $dst_host;
 
272
   }
 
273
   elsif ( $dst_host =~ m/:$self->{port}$/ ) {
 
274
      $packet_from = 'client';
 
275
      $client      = $src_host;
 
276
   }
 
277
   else {
 
278
      MKDEBUG && _d('Packet is not to or from a MySQL server');
 
279
      return;
 
280
   }
 
281
   MKDEBUG && _d('Client', $client);
 
282
 
 
283
   # Get the client's session info or create a new session if
 
284
   # we catch the TCP SYN sequence or the packetno is 0.
 
285
   my $packetno = -1;
 
286
   if ( $packet->{data_len} >= 5 ) {
 
287
      # 5 bytes is the minimum length of any valid MySQL packet.
 
288
      # If there's less, it's probably some TCP control packet
 
289
      # with other data.  Peek at the MySQL packet number.  The
 
290
      # only time a server sends packetno 0 is for its handshake.
 
291
      # Client packetno 0 marks start of new query.
 
292
      $packetno = to_num(substr($packet->{data}, 6, 2));
 
293
   }
 
294
   if ( !exists $self->{sessions}->{$client} ) {
 
295
      if ( $packet->{syn} ) {
 
296
         MKDEBUG && _d('New session (SYN)');
 
297
      }
 
298
      elsif ( $packetno == 0 ) {
 
299
         MKDEBUG && _d('New session (packetno 0)');
 
300
      }
 
301
      else {
 
302
         MKDEBUG && _d('Ignoring mid-stream', $packet_from, 'data,',
 
303
            'packetno', $packetno);
 
304
         return;
 
305
      }
 
306
 
 
307
      $self->{sessions}->{$client} = {
 
308
         client        => $client,
 
309
         ts            => $packet->{ts},
 
310
         state         => undef,
 
311
         compress      => undef,
 
312
         raw_packets   => [],
 
313
         buff          => '',
 
314
         sths          => {},
 
315
         attribs       => {},
 
316
         n_queries     => 0,
 
317
      };
 
318
   }
 
319
   my $session = $self->{sessions}->{$client};
 
320
   MKDEBUG && _d('Client state:', $session->{state});
 
321
 
 
322
   # Save raw packets to dump later in case something fails.
 
323
   push @{$session->{raw_packets}}, $packet->{raw_packet};
 
324
 
 
325
   # Check client port reuse.
 
326
   # http://code.google.com/p/maatkit/issues/detail?id=794
 
327
   if ( $packet->{syn} && ($session->{n_queries} > 0 || $session->{state}) ) {
 
328
      MKDEBUG && _d('Client port reuse and last session did not quit');
 
329
      # Fail the session so we can see the last thing the previous
 
330
      # session was doing.
 
331
      $self->fail_session($session,
 
332
            'client port reuse and last session did not quit');
 
333
      # Then recurse to create a New session.
 
334
      return $self->parse_event(%args);
 
335
   }
 
336
 
 
337
   # Return early if there's no TCP/MySQL data.  These are usually
 
338
   # TCP control packets: SYN, ACK, FIN, etc.
 
339
   if ( $packet->{data_len} == 0 ) {
 
340
      MKDEBUG && _d('TCP control:',
 
341
         map { uc $_ } grep { $packet->{$_} } qw(syn ack fin rst));
 
342
      return;
 
343
   }
 
344
 
 
345
   # Return unless the compressed packet can be uncompressed.
 
346
   # If it cannot, then we're helpless and must return.
 
347
   if ( $session->{compress} ) {
 
348
      return unless $self->uncompress_packet($packet, $session);
 
349
   }
 
350
 
 
351
   if ( $session->{buff} && $packet_from eq 'client' ) {
 
352
      # Previous packets were not complete so append this data
 
353
      # to what we've been buffering.  Afterwards, do *not* attempt
 
354
      # to remove_mysql_header() because it was already done (from
 
355
      # the first packet).
 
356
      $session->{buff}      .= $packet->{data};
 
357
      $packet->{data}        = $session->{buff};
 
358
      $session->{buff_left} -= $packet->{data_len};
 
359
 
 
360
      # We didn't remove_mysql_header(), so mysql_data_len isn't set.
 
361
      # So set it to the real, complete data len (from the first
 
362
      # packet's MySQL header).
 
363
      $packet->{mysql_data_len} = $session->{mysql_data_len};
 
364
      $packet->{number}         = $session->{number};
 
365
 
 
366
      MKDEBUG && _d('Appending data to buff; expecting',
 
367
         $session->{buff_left}, 'more bytes');
 
368
   }
 
369
   else { 
 
370
      # Remove the first MySQL header.  A single TCP packet can contain many
 
371
      # MySQL packets, but we only look at the first.  The 2nd and subsequent
 
372
      # packets are usually parts of a result set returned by the server, but
 
373
      # we're not interested in result sets.
 
374
      eval {
 
375
         remove_mysql_header($packet);
 
376
      };
 
377
      if ( $EVAL_ERROR ) {
 
378
         MKDEBUG && _d('remove_mysql_header() failed; failing session');
 
379
         $session->{EVAL_ERROR} = $EVAL_ERROR;
 
380
         $self->fail_session($session, 'remove_mysql_header() failed');
 
381
         return;
 
382
      }
 
383
   }
 
384
 
 
385
   # Finally, parse the packet and maybe create an event.
 
386
   # The returned event may be empty if no event was ready to be created.
 
387
   my $event;
 
388
   if ( $packet_from eq 'server' ) {
 
389
      $event = $self->_packet_from_server($packet, $session, $args{misc});
 
390
   }
 
391
   elsif ( $packet_from eq 'client' ) {
 
392
      if ( $session->{buff} ) {
 
393
         if ( $session->{buff_left} <= 0 ) {
 
394
            MKDEBUG && _d('Data is complete');
 
395
            $self->_delete_buff($session);
 
396
         }
 
397
         else {
 
398
            return;  # waiting for more data; buff_left was reported earlier
 
399
         }
 
400
      }
 
401
      elsif ( $packet->{mysql_data_len} > ($packet->{data_len} - 4) ) {
 
402
 
 
403
         # http://code.google.com/p/maatkit/issues/detail?id=832
 
404
         if ( $session->{cmd} && ($session->{state} || '') eq 'awaiting_reply' ) {
 
405
            MKDEBUG && _d('No server OK to previous command (frag)');
 
406
            $self->fail_session($session, 'no server OK to previous command');
 
407
            # The MySQL header is removed by this point, so put it back.
 
408
            $packet->{data} = $packet->{mysql_hdr} . $packet->{data};
 
409
            return $self->parse_event(%args);
 
410
         }
 
411
 
 
412
         # There is more MySQL data than this packet contains.
 
413
         # Save the data and the original MySQL header values
 
414
         # then wait for the rest of the data.
 
415
         $session->{buff}           = $packet->{data};
 
416
         $session->{mysql_data_len} = $packet->{mysql_data_len};
 
417
         $session->{number}         = $packet->{number};
 
418
 
 
419
         # Do this just once here.  For the next packets, buff_left
 
420
         # will be decremented above.
 
421
         $session->{buff_left}
 
422
            ||= $packet->{mysql_data_len} - ($packet->{data_len} - 4);
 
423
 
 
424
         MKDEBUG && _d('Data not complete; expecting',
 
425
            $session->{buff_left}, 'more bytes');
 
426
         return;
 
427
      }
 
428
 
 
429
      if ( $session->{cmd} && ($session->{state} || '') eq 'awaiting_reply' ) {
 
430
         # Buffer handling above should ensure that by this point we have
 
431
         # the full client query.  If there's a previous client query for
 
432
         # which we're "awaiting_reply" and then we get another client
 
433
         # query, chances are we missed the server's OK response to the
 
434
         # first query.  So fail the first query and re-parse this second
 
435
         # query.
 
436
         MKDEBUG && _d('No server OK to previous command');
 
437
         $self->fail_session($session, 'no server OK to previous command');
 
438
         # The MySQL header is removed by this point, so put it back.
 
439
         $packet->{data} = $packet->{mysql_hdr} . $packet->{data};
 
440
         return $self->parse_event(%args);
 
441
      }
 
442
 
 
443
      $event = $self->_packet_from_client($packet, $session, $args{misc});
 
444
   }
 
445
   else {
 
446
      # Should not get here.
 
447
      die 'Packet origin unknown';
 
448
   }
 
449
 
 
450
   MKDEBUG && _d('Done parsing packet; client state:', $session->{state});
 
451
   if ( $session->{closed} ) {
 
452
      delete $self->{sessions}->{$session->{client}};
 
453
      MKDEBUG && _d('Session deleted');
 
454
   }
 
455
 
 
456
   $args{stats}->{events_parsed}++ if $args{stats};
 
457
   return $event;
 
458
}
 
459
 
 
460
# Handles a packet from the server given the state of the session.
 
461
# The server can send back a lot of different stuff, but luckily
 
462
# we're only interested in
 
463
#    * Connection handshake packets for the thread_id
 
464
#    * OK and Error packets for errors, warnings, etc.
 
465
# Anything else is ignored.  Returns an event if one was ready to be
 
466
# created, otherwise returns nothing.
 
467
sub _packet_from_server {
 
468
   my ( $self, $packet, $session, $misc ) = @_;
 
469
   die "I need a packet"  unless $packet;
 
470
   die "I need a session" unless $session;
 
471
 
 
472
   MKDEBUG && _d('Packet is from server; client state:', $session->{state}); 
 
473
 
 
474
   if ( ($session->{server_seq} || '') eq $packet->{seq} ) {
 
475
      push @{ $session->{server_retransmissions} }, $packet->{seq};
 
476
      MKDEBUG && _d('TCP retransmission');
 
477
      return;
 
478
   }
 
479
   $session->{server_seq} = $packet->{seq};
 
480
 
 
481
   my $data = $packet->{data};
 
482
 
 
483
   # The first byte in the packet indicates whether it's an OK,
 
484
   # ERROR, EOF packet.  If it's not one of those, we test
 
485
   # whether it's an initialization packet (the first thing the
 
486
   # server ever sends the client).  If it's not that, it could
 
487
   # be a result set header, field, row data, etc.
 
488
 
 
489
   my ( $first_byte ) = substr($data, 0, 2, '');
 
490
   MKDEBUG && _d('First byte of packet:', $first_byte);
 
491
   if ( !$first_byte ) {
 
492
      $self->fail_session($session, 'no first byte');
 
493
      return;
 
494
   }
 
495
 
 
496
   # If there's no session state, then we're catching a server response
 
497
   # mid-stream.  It's only safe to wait until the client sends a command
 
498
   # or to look for the server handshake.
 
499
   if ( !$session->{state} ) {
 
500
      if ( $first_byte eq '0a' && length $data >= 33 && $data =~ m/00{13}/ ) {
 
501
         # It's the handshake packet from the server to the client.
 
502
         # 0a is protocol v10 which is essentially the only version used
 
503
         # today.  33 is the minimum possible length for a valid server
 
504
         # handshake packet.  It's probably a lot longer.  Other packets
 
505
         # may start with 0a, but none that can would be >= 33.  The 13-byte
 
506
         # 00 scramble buffer is another indicator.
 
507
         my $handshake = parse_server_handshake_packet($data);
 
508
         if ( !$handshake ) {
 
509
            $self->fail_session($session, 'failed to parse server handshake');
 
510
            return;
 
511
         }
 
512
         $session->{state}     = 'server_handshake';
 
513
         $session->{thread_id} = $handshake->{thread_id};
 
514
 
 
515
         # See http://code.google.com/p/maatkit/issues/detail?id=794
 
516
         $session->{ts} = $packet->{ts} unless $session->{ts};
 
517
      }
 
518
      elsif ( $session->{buff} ) {
 
519
         $self->fail_session($session,
 
520
            'got server response before full buffer');
 
521
         return;
 
522
      }
 
523
      else {
 
524
         MKDEBUG && _d('Ignoring mid-stream server response');
 
525
         return;
 
526
      }
 
527
   }
 
528
   else {
 
529
      if ( $first_byte eq '00' ) { 
 
530
         if ( ($session->{state} || '') eq 'client_auth' ) {
 
531
            # We logged in OK!  Trigger an admin Connect command.
 
532
 
 
533
            $session->{compress} = $session->{will_compress};
 
534
            delete $session->{will_compress};
 
535
            MKDEBUG && $session->{compress} && _d('Packets will be compressed');
 
536
 
 
537
            MKDEBUG && _d('Admin command: Connect');
 
538
            return $self->_make_event(
 
539
               {  cmd => 'Admin',
 
540
                  arg => 'administrator command: Connect',
 
541
                  ts  => $packet->{ts}, # Events are timestamped when they end
 
542
               },
 
543
               $packet, $session
 
544
            );
 
545
         }
 
546
         elsif ( $session->{cmd} ) {
 
547
            # This OK should be ack'ing a query or something sent earlier
 
548
            # by the client.  OK for prepared statement are special.
 
549
            my $com = $session->{cmd}->{cmd};
 
550
            my $ok;
 
551
            if ( $com eq COM_STMT_PREPARE ) {
 
552
               MKDEBUG && _d('OK for prepared statement');
 
553
               $ok = parse_ok_prepared_statement_packet($data);
 
554
               if ( !$ok ) {
 
555
                  $self->fail_session($session,
 
556
                     'failed to parse OK prepared statement packet');
 
557
                  return;
 
558
               }
 
559
               my $sth_id = $ok->{sth_id};
 
560
               $session->{attribs}->{Statement_id} = $sth_id;
 
561
 
 
562
               # Save all sth info, used in parse_execute_packet().
 
563
               $session->{sths}->{$sth_id} = $ok;
 
564
               $session->{sths}->{$sth_id}->{statement}
 
565
                  = $session->{cmd}->{arg};
 
566
            }
 
567
            else {
 
568
               $ok  = parse_ok_packet($data);
 
569
               if ( !$ok ) {
 
570
                  $self->fail_session($session, 'failed to parse OK packet');
 
571
                  return;
 
572
               }
 
573
            }
 
574
 
 
575
            my $arg;
 
576
            if ( $com eq COM_QUERY
 
577
                 || $com eq COM_STMT_EXECUTE || $com eq COM_STMT_RESET ) {
 
578
               $com = 'Query';
 
579
               $arg = $session->{cmd}->{arg};
 
580
            }
 
581
            elsif ( $com eq COM_STMT_PREPARE ) {
 
582
               $com = 'Query';
 
583
               $arg = "PREPARE $session->{cmd}->{arg}";
 
584
            }
 
585
            else {
 
586
               $arg = 'administrator command: '
 
587
                    . ucfirst(lc(substr($com_for{$com}, 4)));
 
588
               $com = 'Admin';
 
589
            }
 
590
 
 
591
            return $self->_make_event(
 
592
               {  cmd           => $com,
 
593
                  arg           => $arg,
 
594
                  ts            => $packet->{ts},
 
595
                  Insert_id     => $ok->{insert_id},
 
596
                  Warning_count => $ok->{warnings},
 
597
                  Rows_affected => $ok->{affected_rows},
 
598
               },
 
599
               $packet, $session
 
600
            );
 
601
         } 
 
602
         else {
 
603
            MKDEBUG && _d('Looks like an OK packet but session has no cmd');
 
604
         }
 
605
      }
 
606
      elsif ( $first_byte eq 'ff' ) {
 
607
         my $error = parse_error_packet($data);
 
608
         if ( !$error ) {
 
609
            $self->fail_session($session, 'failed to parse error packet');
 
610
            return;
 
611
         }
 
612
         my $event;
 
613
 
 
614
         if ( $session->{state} eq 'client_auth' ) {
 
615
            MKDEBUG && _d('Connection failed');
 
616
            $event = {
 
617
               cmd      => 'Admin',
 
618
               arg      => 'administrator command: Connect',
 
619
               ts       => $packet->{ts},
 
620
               Error_no => $error->{errno},
 
621
            };
 
622
            $session->{attribs}->{Error_msg} = $error->{message};
 
623
            $session->{closed} = 1;  # delete session when done
 
624
            return $self->_make_event($event, $packet, $session);
 
625
         }
 
626
         elsif ( $session->{cmd} ) {
 
627
            # This error should be in response to a query or something
 
628
            # sent earlier by the client.
 
629
            my $com = $session->{cmd}->{cmd};
 
630
            my $arg;
 
631
 
 
632
            if ( $com eq COM_QUERY || $com eq COM_STMT_EXECUTE ) {
 
633
               $com = 'Query';
 
634
               $arg = $session->{cmd}->{arg};
 
635
            }
 
636
            else {
 
637
               $arg = 'administrator command: '
 
638
                    . ucfirst(lc(substr($com_for{$com}, 4)));
 
639
               $com = 'Admin';
 
640
            }
 
641
 
 
642
            $event = {
 
643
               cmd       => $com,
 
644
               arg       => $arg,
 
645
               ts        => $packet->{ts},
 
646
               Error_no  => $error->{errno} ? "#$error->{errno}" : 'none',
 
647
            };
 
648
            $session->{attribs}->{Error_msg} = $error->{message};
 
649
            return $self->_make_event($event, $packet, $session);
 
650
         }
 
651
         else {
 
652
            MKDEBUG && _d('Looks like an error packet but client is not '
 
653
               . 'authenticating and session has no cmd');
 
654
         }
 
655
      }
 
656
      elsif ( $first_byte eq 'fe' && $packet->{mysql_data_len} < 9 ) {
 
657
         # EOF packet
 
658
         if ( $packet->{mysql_data_len} == 1
 
659
              && $session->{state} eq 'client_auth'
 
660
              && $packet->{number} == 2 )
 
661
         {
 
662
            MKDEBUG && _d('Server has old password table;',
 
663
               'client will resend password using old algorithm');
 
664
            $session->{state} = 'client_auth_resend';
 
665
         }
 
666
         else {
 
667
            MKDEBUG && _d('Got an EOF packet');
 
668
            $self->fail_session($session, 'got an unexpected EOF packet');
 
669
            # ^^^ We shouldn't reach this because EOF should come after a
 
670
            # header, field, or row data packet; and we should be firing the
 
671
            # event and returning when we see that.  See SVN history for some
 
672
            # good stuff we could do if we wanted to handle EOF packets.
 
673
         }
 
674
      }
 
675
      else {
 
676
         # Since we do NOT always have all the data the server sent to the
 
677
         # client, we can't always do any processing of results.  So when
 
678
         # we get one of these, we just fire the event even if the query
 
679
         # is not done.  This means we will NOT process EOF packets
 
680
         # themselves (see above).
 
681
         if ( $session->{cmd} ) {
 
682
            MKDEBUG && _d('Got a row/field/result packet');
 
683
            my $com = $session->{cmd}->{cmd};
 
684
            MKDEBUG && _d('Responding to client', $com_for{$com});
 
685
            my $event = { ts  => $packet->{ts} };
 
686
            if ( $com eq COM_QUERY || $com eq COM_STMT_EXECUTE ) {
 
687
               $event->{cmd} = 'Query';
 
688
               $event->{arg} = $session->{cmd}->{arg};
 
689
            }
 
690
            else {
 
691
               $event->{arg} = 'administrator command: '
 
692
                    . ucfirst(lc(substr($com_for{$com}, 4)));
 
693
               $event->{cmd} = 'Admin';
 
694
            }
 
695
 
 
696
            # We DID get all the data in the packet.
 
697
            if ( $packet->{complete} ) {
 
698
               # Look to see if the end of the data appears to be an EOF
 
699
               # packet.
 
700
               my ( $warning_count, $status_flags )
 
701
                  = $data =~ m/fe(.{4})(.{4})\Z/;
 
702
               if ( $warning_count ) { 
 
703
                  $event->{Warnings} = to_num($warning_count);
 
704
                  my $flags = to_num($status_flags); # TODO set all flags?
 
705
                  $event->{No_good_index_used}
 
706
                     = $flags & SERVER_QUERY_NO_GOOD_INDEX_USED ? 1 : 0;
 
707
                  $event->{No_index_used}
 
708
                     = $flags & SERVER_QUERY_NO_INDEX_USED ? 1 : 0;
 
709
               }
 
710
            }
 
711
 
 
712
            return $self->_make_event($event, $packet, $session);
 
713
         }
 
714
         else {
 
715
            MKDEBUG && _d('Unknown in-stream server response');
 
716
         }
 
717
      }
 
718
   }
 
719
 
 
720
   return;
 
721
}
 
722
 
 
723
# Handles a packet from the client given the state of the session.
 
724
# The client doesn't send a wide and exotic array of packets like
 
725
# the server.  Even so, we're only interested in:
 
726
#    * Users and dbs from connection handshake packets
 
727
#    * SQL statements from COM_QUERY commands
 
728
# Anything else is ignored.  Returns an event if one was ready to be
 
729
# created, otherwise returns nothing.
 
730
sub _packet_from_client {
 
731
   my ( $self, $packet, $session, $misc ) = @_;
 
732
   die "I need a packet"  unless $packet;
 
733
   die "I need a session" unless $session;
 
734
 
 
735
   MKDEBUG && _d('Packet is from client; state:', $session->{state}); 
 
736
 
 
737
   if ( ($session->{client_seq} || '') eq $packet->{seq} ) {
 
738
      push @{ $session->{client_retransmissions} }, $packet->{seq};
 
739
      MKDEBUG && _d('TCP retransmission');
 
740
      return;
 
741
   }
 
742
   $session->{client_seq} = $packet->{seq};
 
743
 
 
744
   my $data  = $packet->{data};
 
745
   my $ts    = $packet->{ts};
 
746
 
 
747
   if ( ($session->{state} || '') eq 'server_handshake' ) {
 
748
      MKDEBUG && _d('Expecting client authentication packet');
 
749
      # The connection is a 3-way handshake:
 
750
      #    server > client  (protocol version, thread id, etc.)
 
751
      #    client > server  (user, pass, default db, etc.)
 
752
      #    server > client  OK if login succeeds
 
753
      # pos_in_log refers to 2nd handshake from the client.
 
754
      # A connection is logged even if the client fails to
 
755
      # login (bad password, etc.).
 
756
      my $handshake = parse_client_handshake_packet($data);
 
757
      if ( !$handshake ) {
 
758
         $self->fail_session($session, 'failed to parse client handshake');
 
759
         return;
 
760
      }
 
761
      $session->{state}         = 'client_auth';
 
762
      $session->{pos_in_log}    = $packet->{pos_in_log};
 
763
      $session->{user}          = $handshake->{user};
 
764
      $session->{db}            = $handshake->{db};
 
765
 
 
766
      # $session->{will_compress} will become $session->{compress} when
 
767
      # the server's final handshake packet is received.  This prevents
 
768
      # parse_packet() from trying to decompress that final packet.
 
769
      # Compressed packets can only begin after the full handshake is done.
 
770
      $session->{will_compress} = $handshake->{flags}->{CLIENT_COMPRESS};
 
771
   }
 
772
   elsif ( ($session->{state} || '') eq 'client_auth_resend' ) {
 
773
      # Don't know how to parse this packet.
 
774
      MKDEBUG && _d('Client resending password using old algorithm');
 
775
      $session->{state} = 'client_auth';
 
776
   }
 
777
   elsif ( ($session->{state} || '') eq 'awaiting_reply' ) {
 
778
      my $arg = $session->{cmd}->{arg} ? substr($session->{cmd}->{arg}, 0, 50)
 
779
              : 'unknown';
 
780
      MKDEBUG && _d('More data for previous command:', $arg, '...'); 
 
781
      return;
 
782
   }
 
783
   else {
 
784
      # Otherwise, it should be a query if its the first packet (number 0).
 
785
      # We ignore the commands that take arguments (COM_CHANGE_USER,
 
786
      # COM_PROCESS_KILL).
 
787
      if ( $packet->{number} != 0 ) {
 
788
         $self->fail_session($session, 'client cmd not packet 0');
 
789
         return;
 
790
      }
 
791
 
 
792
      # Detect compression in-stream only if $session->{compress} is
 
793
      # not defined.  This means we didn't see the client handshake.
 
794
      # If we had seen it, $session->{compress} would be defined as 0 or 1.
 
795
      if ( !defined $session->{compress} ) {
 
796
         return unless $self->detect_compression($packet, $session);
 
797
         $data = $packet->{data};
 
798
      }
 
799
 
 
800
      my $com = parse_com_packet($data, $packet->{mysql_data_len});
 
801
      if ( !$com ) {
 
802
         $self->fail_session($session, 'failed to parse COM packet');
 
803
         return;
 
804
      }
 
805
 
 
806
      if ( $com->{code} eq COM_STMT_EXECUTE ) {
 
807
         MKDEBUG && _d('Execute prepared statement');
 
808
         my $exec = parse_execute_packet($com->{data}, $session->{sths});
 
809
         if ( !$exec ) {
 
810
            # This does not signal a failure, it could just be that
 
811
            # the statement handle ID is unknown.
 
812
            MKDEBUG && _d('Failed to parse execute packet');
 
813
            $session->{state} = undef;
 
814
            return;
 
815
         }
 
816
         $com->{data} = $exec->{arg};
 
817
         $session->{attribs}->{Statement_id} = $exec->{sth_id};
 
818
      }
 
819
      elsif ( $com->{code} eq COM_STMT_RESET ) {
 
820
         my $sth_id = get_sth_id($com->{data});
 
821
         if ( !$sth_id ) {
 
822
            $self->fail_session($session,
 
823
               'failed to parse prepared statement reset packet');
 
824
            return;
 
825
         }
 
826
         $com->{data} = "RESET $sth_id";
 
827
         $session->{attribs}->{Statement_id} = $sth_id;
 
828
      }
 
829
 
 
830
      $session->{state}      = 'awaiting_reply';
 
831
      $session->{pos_in_log} = $packet->{pos_in_log};
 
832
      $session->{ts}         = $ts;
 
833
      $session->{cmd}        = {
 
834
         cmd => $com->{code},
 
835
         arg => $com->{data},
 
836
      };
 
837
 
 
838
      if ( $com->{code} eq COM_QUIT ) { # Fire right away; will cleanup later.
 
839
         MKDEBUG && _d('Got a COM_QUIT');
 
840
 
 
841
         # See http://code.google.com/p/maatkit/issues/detail?id=794
 
842
         $session->{closed} = 1;  # delete session when done
 
843
 
 
844
         return $self->_make_event(
 
845
            {  cmd       => 'Admin',
 
846
               arg       => 'administrator command: Quit',
 
847
               ts        => $ts,
 
848
            },
 
849
            $packet, $session
 
850
         );
 
851
      }
 
852
      elsif ( $com->{code} eq COM_STMT_CLOSE ) {
 
853
         # Apparently, these are not acknowledged by the server.
 
854
         my $sth_id = get_sth_id($com->{data});
 
855
         if ( !$sth_id ) {
 
856
            $self->fail_session($session,
 
857
               'failed to parse prepared statement close packet');
 
858
            return;
 
859
         }
 
860
         delete $session->{sths}->{$sth_id};
 
861
         return $self->_make_event(
 
862
            {  cmd       => 'Query',
 
863
               arg       => "DEALLOCATE PREPARE $sth_id",
 
864
               ts        => $ts,
 
865
            },
 
866
            $packet, $session
 
867
         );
 
868
      }
 
869
   }
 
870
 
 
871
   return;
 
872
}
 
873
 
 
874
# Make and return an event from the given packet and session.
 
875
sub _make_event {
 
876
   my ( $self, $event, $packet, $session ) = @_;
 
877
   MKDEBUG && _d('Making event');
 
878
 
 
879
   # Clear packets that preceded this event.
 
880
   $session->{raw_packets}  = [];
 
881
   $self->_delete_buff($session);
 
882
 
 
883
   if ( !$session->{thread_id} ) {
 
884
      # Only the server handshake packet gives the thread id, so for
 
885
      # sessions caught mid-stream we assign a fake thread id.
 
886
      MKDEBUG && _d('Giving session fake thread id', $self->{fake_thread_id});
 
887
      $session->{thread_id} = $self->{fake_thread_id}++;
 
888
   }
 
889
 
 
890
   my ($host, $port) = $session->{client} =~ m/((?:\d+\.){3}\d+)\:(\w+)/;
 
891
   my $new_event = {
 
892
      cmd        => $event->{cmd},
 
893
      arg        => $event->{arg},
 
894
      bytes      => length( $event->{arg} ),
 
895
      ts         => tcp_timestamp( $event->{ts} ),
 
896
      host       => $host,
 
897
      ip         => $host,
 
898
      port       => $port,
 
899
      db         => $session->{db},
 
900
      user       => $session->{user},
 
901
      Thread_id  => $session->{thread_id},
 
902
      pos_in_log => $session->{pos_in_log},
 
903
      Query_time => timestamp_diff($session->{ts}, $packet->{ts}),
 
904
      Error_no   => $event->{Error_no} || 'none',
 
905
      Rows_affected      => ($event->{Rows_affected} || 0),
 
906
      Warning_count      => ($event->{Warning_count} || 0),
 
907
      No_good_index_used => ($event->{No_good_index_used} ? 'Yes' : 'No'),
 
908
      No_index_used      => ($event->{No_index_used}      ? 'Yes' : 'No'),
 
909
   };
 
910
   @{$new_event}{keys %{$session->{attribs}}} = values %{$session->{attribs}};
 
911
   MKDEBUG && _d('Properties of event:', Dumper($new_event));
 
912
 
 
913
   # Delete cmd to prevent re-making the same event if the
 
914
   # server sends extra stuff that looks like a result set, etc.
 
915
   delete $session->{cmd};
 
916
 
 
917
   # Undef the session state so that we ignore everything from
 
918
   # the server and wait until the client says something again.
 
919
   $session->{state} = undef;
 
920
 
 
921
   # Clear the attribs for this event.
 
922
   $session->{attribs} = {};
 
923
 
 
924
   $session->{n_queries}++;
 
925
   $session->{server_retransmissions} = [];
 
926
   $session->{client_retransmissions} = [];
 
927
 
 
928
   return $new_event;
 
929
}
 
930
 
 
931
# Extracts a slow-log-formatted timestamp from the tcpdump timestamp format.
 
932
sub tcp_timestamp {
 
933
   my ( $ts ) = @_;
 
934
   $ts =~ s/^\d\d(\d\d)-(\d\d)-(\d\d)/$1$2$3/;
 
935
   return $ts;
 
936
}
 
937
 
 
938
# Returns the difference between two tcpdump timestamps.
 
939
sub timestamp_diff {
 
940
   my ( $start, $end ) = @_;
 
941
   my $sd = substr($start, 0, 11, '');
 
942
   my $ed = substr($end,   0, 11, '');
 
943
   my ( $sh, $sm, $ss ) = split(/:/, $start);
 
944
   my ( $eh, $em, $es ) = split(/:/, $end);
 
945
   my $esecs = ($eh * 3600 + $em * 60 + $es);
 
946
   my $ssecs = ($sh * 3600 + $sm * 60 + $ss);
 
947
   if ( $sd eq $ed ) {
 
948
      return sprintf '%.6f', $esecs - $ssecs;
 
949
   }
 
950
   else { # Assume only one day boundary has been crossed, no DST, etc
 
951
      return sprintf '%.6f', ( 86_400 - $ssecs ) + $esecs;
 
952
   }
 
953
}
 
954
 
 
955
# Converts hexadecimal to string.
 
956
sub to_string {
 
957
   my ( $data ) = @_;
 
958
   return pack('H*', $data);
 
959
}
 
960
 
 
961
sub unpack_string {
 
962
   my ( $data ) = @_;
 
963
   my $len        = 0;
 
964
   my $encode_len = 0;
 
965
   ($data, $len, $encode_len) = decode_len($data);
 
966
   my $t = 'H' . ($len ? $len * 2 : '*');
 
967
   $data = pack($t, $data);
 
968
   return "\"$data\"", $encode_len + $len;
 
969
}
 
970
 
 
971
sub decode_len {
 
972
   my ( $data ) = @_;
 
973
   return unless $data;
 
974
 
 
975
   # first byte hex   len
 
976
   # ========== ====  =============
 
977
   # 0-251      0-FB  Same
 
978
   # 252        FC    Len in next 2
 
979
   # 253        FD    Len in next 4
 
980
   # 254        FE    Len in next 8
 
981
   my $first_byte = to_num(substr($data, 0, 2, ''));
 
982
 
 
983
   my $len;
 
984
   my $encode_len;
 
985
   if ( $first_byte <= 251 ) {
 
986
      $len        = $first_byte;
 
987
      $encode_len = 1;
 
988
   }
 
989
   elsif ( $first_byte == 252 ) {
 
990
      $len        = to_num(substr($data, 4, ''));
 
991
      $encode_len = 2;
 
992
   }
 
993
   elsif ( $first_byte == 253 ) {
 
994
      $len        = to_num(substr($data, 6, ''));
 
995
      $encode_len = 3;
 
996
   }
 
997
   elsif ( $first_byte == 254 ) {
 
998
      $len        = to_num(substr($data, 16, ''));
 
999
      $encode_len = 8;
 
1000
   }
 
1001
   else {
 
1002
      # This shouldn't happen, but it may if we're passed data
 
1003
      # that isn't length encoded.
 
1004
      MKDEBUG && _d('data:', $data, 'first byte:', $first_byte);
 
1005
      die "Invalid length encoded byte: $first_byte";
 
1006
   }
 
1007
 
 
1008
   MKDEBUG && _d('len:', $len, 'encode len', $encode_len);
 
1009
   return $data, $len, $encode_len;
 
1010
}
 
1011
 
 
1012
# All numbers are stored with the least significant byte first in the MySQL
 
1013
# protocol.
 
1014
sub to_num {
 
1015
   my ( $str, $len ) = @_;
 
1016
   if ( $len ) {
 
1017
      $str = substr($str, 0, $len * 2);
 
1018
   }
 
1019
   my @bytes = $str =~ m/(..)/g;
 
1020
   my $result = 0;
 
1021
   foreach my $i ( 0 .. $#bytes ) {
 
1022
      $result += hex($bytes[$i]) * (16 ** ($i * 2));
 
1023
   }
 
1024
   return $result;
 
1025
}
 
1026
 
 
1027
sub to_double {
 
1028
   my ( $str ) = @_;
 
1029
   return unpack('d', pack('H*', $str));
 
1030
}
 
1031
 
 
1032
# Accepts a reference to a string, which it will modify.  Extracts a
 
1033
# length-coded binary off the front of the string and returns that value as an
 
1034
# integer.
 
1035
sub get_lcb {
 
1036
   my ( $string ) = @_;
 
1037
   my $first_byte = hex(substr($$string, 0, 2, ''));
 
1038
   if ( $first_byte < 251 ) {
 
1039
      return $first_byte;
 
1040
   }
 
1041
   elsif ( $first_byte == 252 ) {
 
1042
      return to_num(substr($$string, 0, 4, ''));
 
1043
   }
 
1044
   elsif ( $first_byte == 253 ) {
 
1045
      return to_num(substr($$string, 0, 6, ''));
 
1046
   }
 
1047
   elsif ( $first_byte == 254 ) {
 
1048
      return to_num(substr($$string, 0, 16, ''));
 
1049
   }
 
1050
}
 
1051
 
 
1052
# Error packet structure:
 
1053
# Offset  Bytes               Field
 
1054
# ======  =================   ====================================
 
1055
#         00 00 00 01         MySQL proto header (already removed)
 
1056
#         ff                  Error  (already removed)
 
1057
# 0       00 00               Error number
 
1058
# 4       23                  SQL state marker, always '#'
 
1059
# 6       00 00 00 00 00      SQL state
 
1060
# 16      00 ...              Error message
 
1061
# The sqlstate marker and actual sqlstate are combined into one value. 
 
1062
sub parse_error_packet {
 
1063
   my ( $data ) = @_;
 
1064
   return unless $data;
 
1065
   MKDEBUG && _d('ERROR data:', $data);
 
1066
   if ( length $data < 16 ) {
 
1067
      MKDEBUG && _d('Error packet is too short:', $data);
 
1068
      return;
 
1069
   }
 
1070
   my $errno    = to_num(substr($data, 0, 4));
 
1071
   my $marker   = to_string(substr($data, 4, 2));
 
1072
   return unless $marker eq '#';
 
1073
   my $sqlstate = to_string(substr($data, 6, 10));
 
1074
   my $message  = to_string(substr($data, 16));
 
1075
   my $pkt = {
 
1076
      errno    => $errno,
 
1077
      sqlstate => $marker . $sqlstate,
 
1078
      message  => $message,
 
1079
   };
 
1080
   MKDEBUG && _d('Error packet:', Dumper($pkt));
 
1081
   return $pkt;
 
1082
}
 
1083
 
 
1084
# OK packet structure:
 
1085
# Bytes         Field
 
1086
# ===========   ====================================
 
1087
# 00 00 00 01   MySQL proto header (already removed)
 
1088
# 00            OK/Field count (already removed)
 
1089
# 1-9           Affected rows (LCB)
 
1090
# 1-9           Insert ID (LCB)
 
1091
# 00 00         Server status
 
1092
# 00 00         Warning count
 
1093
# 00 ...        Message (optional)
 
1094
sub parse_ok_packet {
 
1095
   my ( $data ) = @_;
 
1096
   return unless $data;
 
1097
   MKDEBUG && _d('OK data:', $data);
 
1098
   if ( length $data < 12 ) {
 
1099
      MKDEBUG && _d('OK packet is too short:', $data);
 
1100
      return;
 
1101
   }
 
1102
   my $affected_rows = get_lcb(\$data);
 
1103
   my $insert_id     = get_lcb(\$data);
 
1104
   my $status        = to_num(substr($data, 0, 4, ''));
 
1105
   my $warnings      = to_num(substr($data, 0, 4, ''));
 
1106
   my $message       = to_string($data);
 
1107
   # Note: $message is discarded.  It might be something like
 
1108
   # Records: 2  Duplicates: 0  Warnings: 0
 
1109
   my $pkt = {
 
1110
      affected_rows => $affected_rows,
 
1111
      insert_id     => $insert_id,
 
1112
      status        => $status,
 
1113
      warnings      => $warnings,
 
1114
      message       => $message,
 
1115
   };
 
1116
   MKDEBUG && _d('OK packet:', Dumper($pkt));
 
1117
   return $pkt;
 
1118
}
 
1119
 
 
1120
# OK prepared statement packet structure:
 
1121
# Bytes         Field
 
1122
# ===========   ====================================
 
1123
# 00            OK  (already removed)
 
1124
# 00 00 00 00   Statement handler ID
 
1125
# 00 00         Number of columns in result set
 
1126
# 00 00         Number of parameters (?) in query
 
1127
sub parse_ok_prepared_statement_packet {
 
1128
   my ( $data ) = @_;
 
1129
   return unless $data;
 
1130
   MKDEBUG && _d('OK prepared statement data:', $data);
 
1131
   if ( length $data < 8 ) {
 
1132
      MKDEBUG && _d('OK prepared statement packet is too short:', $data);
 
1133
      return;
 
1134
   }
 
1135
   my $sth_id     = to_num(substr($data, 0, 8, ''));
 
1136
   my $num_cols   = to_num(substr($data, 0, 4, ''));
 
1137
   my $num_params = to_num(substr($data, 0, 4, ''));
 
1138
   my $pkt = {
 
1139
      sth_id     => $sth_id,
 
1140
      num_cols   => $num_cols,
 
1141
      num_params => $num_params,
 
1142
   };
 
1143
   MKDEBUG && _d('OK prepared packet:', Dumper($pkt));
 
1144
   return $pkt;
 
1145
}
 
1146
 
 
1147
# Currently we only capture and return the thread id.
 
1148
sub parse_server_handshake_packet {
 
1149
   my ( $data ) = @_;
 
1150
   return unless $data;
 
1151
   MKDEBUG && _d('Server handshake data:', $data);
 
1152
   my $handshake_pattern = qr{
 
1153
                        # Bytes                Name
 
1154
      ^                 # -----                ----
 
1155
      (.+?)00           # n Null-Term String   server_version
 
1156
      (.{8})            # 4                    thread_id
 
1157
      .{16}             # 8                    scramble_buff
 
1158
      .{2}              # 1                    filler: always 0x00
 
1159
      (.{4})            # 2                    server_capabilities
 
1160
      .{2}              # 1                    server_language
 
1161
      .{4}              # 2                    server_status
 
1162
      .{26}             # 13                   filler: always 0x00
 
1163
                        # 13                   rest of scramble_buff
 
1164
   }x;
 
1165
   my ( $server_version, $thread_id, $flags ) = $data =~ m/$handshake_pattern/;
 
1166
   my $pkt = {
 
1167
      server_version => to_string($server_version),
 
1168
      thread_id      => to_num($thread_id),
 
1169
      flags          => parse_flags($flags),
 
1170
   };
 
1171
   MKDEBUG && _d('Server handshake packet:', Dumper($pkt));
 
1172
   return $pkt;
 
1173
}
 
1174
 
 
1175
# Currently we only capture and return the user and default database.
 
1176
sub parse_client_handshake_packet {
 
1177
   my ( $data ) = @_;
 
1178
   return unless $data;
 
1179
   MKDEBUG && _d('Client handshake data:', $data);
 
1180
   my ( $flags, $user, $buff_len ) = $data =~ m{
 
1181
      ^
 
1182
      (.{8})         # Client flags
 
1183
      .{10}          # Max packet size, charset
 
1184
      (?:00){23}     # Filler
 
1185
      ((?:..)+?)00   # Null-terminated user name
 
1186
      (..)           # Length-coding byte for scramble buff
 
1187
   }x;
 
1188
 
 
1189
   # This packet is easy to detect because it's the only case where
 
1190
   # the server sends the client a packet first (its handshake) and
 
1191
   # then the client only and ever sends back its handshake.
 
1192
   if ( !$buff_len ) {
 
1193
      MKDEBUG && _d('Did not match client handshake packet');
 
1194
      return;
 
1195
   }
 
1196
 
 
1197
   # This length-coded binary doesn't seem to be a normal one, it
 
1198
   # seems more like a length-coded string actually.
 
1199
   my $code_len = hex($buff_len);
 
1200
   my ( $db ) = $data =~ m!
 
1201
      ^.{64}${user}00..   # Everything matched before
 
1202
      (?:..){$code_len}   # The scramble buffer
 
1203
      (.*)00\Z            # The database name
 
1204
   !x;
 
1205
   my $pkt = {
 
1206
      user  => to_string($user),
 
1207
      db    => $db ? to_string($db) : '',
 
1208
      flags => parse_flags($flags),
 
1209
   };
 
1210
   MKDEBUG && _d('Client handshake packet:', Dumper($pkt));
 
1211
   return $pkt;
 
1212
}
 
1213
 
 
1214
# COM data is not 00-terminated, but the the MySQL client appends \0,
 
1215
# so we have to use the packet length to know where the data ends.
 
1216
sub parse_com_packet {
 
1217
   my ( $data, $len ) = @_;
 
1218
   return unless $data && $len;
 
1219
   MKDEBUG && _d('COM data:',
 
1220
      (substr($data, 0, 100).(length $data > 100 ? '...' : '')),
 
1221
      'len:', $len);
 
1222
   my $code = substr($data, 0, 2);
 
1223
   my $com  = $com_for{$code};
 
1224
   if ( !$com ) {
 
1225
      MKDEBUG && _d('Did not match COM packet');
 
1226
      return;
 
1227
   }
 
1228
   if (    $code ne COM_STMT_EXECUTE
 
1229
        && $code ne COM_STMT_CLOSE
 
1230
        && $code ne COM_STMT_RESET )
 
1231
   {
 
1232
      # Data for the most common COM, e.g. COM_QUERY, is text.
 
1233
      # COM_STMT_EXECUTE is not, so we leave it binary; it can
 
1234
      # be parsed by parse_execute_packet().
 
1235
      $data = to_string(substr($data, 2, ($len - 1) * 2));
 
1236
   }
 
1237
   my $pkt = {
 
1238
      code => $code,
 
1239
      com  => $com,
 
1240
      data => $data,
 
1241
   };
 
1242
   MKDEBUG && _d('COM packet:', Dumper($pkt));
 
1243
   return $pkt;
 
1244
}
 
1245
 
 
1246
# Execute prepared statement packet structure:
 
1247
# Bytes              Field
 
1248
# ===========        ========================================
 
1249
# 00                 Code 17, COM_STMT_EXECUTE
 
1250
# 00 00 00 00        Statement handler ID
 
1251
# 00                 flags
 
1252
# 00 00 00 00        Iteration count (reserved, always 1)
 
1253
# (param_count+7)/8  NULL bitmap
 
1254
# 00                 1 if new parameters, else 0
 
1255
# n*2                Parameter types (only if new parameters)
 
1256
sub parse_execute_packet {
 
1257
   my ( $data, $sths ) = @_;
 
1258
   return unless $data && $sths;
 
1259
 
 
1260
   my $sth_id = to_num(substr($data, 2, 8));
 
1261
   return unless defined $sth_id;
 
1262
 
 
1263
   my $sth = $sths->{$sth_id};
 
1264
   if ( !$sth ) {
 
1265
      MKDEBUG && _d('Skipping unknown statement handle', $sth_id);
 
1266
      return;
 
1267
   }
 
1268
   my $null_count  = int(($sth->{num_params} + 7) / 8) || 1;
 
1269
   my $null_bitmap = to_num(substr($data, 20, $null_count * 2));
 
1270
   MKDEBUG && _d('NULL bitmap:', $null_bitmap, 'count:', $null_count);
 
1271
   
 
1272
   # This chops off everything up to the byte for new params.
 
1273
   substr($data, 0, 20 + ($null_count * 2), '');
 
1274
 
 
1275
   my $new_params = to_num(substr($data, 0, 2, ''));
 
1276
   my @types; 
 
1277
   if ( $new_params ) {
 
1278
      MKDEBUG && _d('New param types');
 
1279
      # It seems all params are type 254, MYSQL_TYPE_STRING.  Perhaps
 
1280
      # this depends on the client.  If we ever need these types, they
 
1281
      # can be saved here.  Otherwise for now I just want to see the
 
1282
      # types in debug output.
 
1283
      for my $i ( 0..($sth->{num_params}-1) ) {
 
1284
         my $type = to_num(substr($data, 0, 4, ''));
 
1285
         push @types, $type_for{$type};
 
1286
         MKDEBUG && _d('Param', $i, 'type:', $type, $type_for{$type});
 
1287
      }
 
1288
      $sth->{types} = \@types;
 
1289
   }
 
1290
   else {
 
1291
      # Retrieve previous param types if there are param vals (data).
 
1292
      @types = @{$sth->{types}} if $data;
 
1293
   }
 
1294
 
 
1295
   # $data should now be truncated up to the parameter values.
 
1296
 
 
1297
   my $arg  = $sth->{statement};
 
1298
   MKDEBUG && _d('Statement:', $arg);
 
1299
   for my $i ( 0..($sth->{num_params}-1) ) {
 
1300
      my $val;
 
1301
      my $len;  # in bytes
 
1302
      if ( $null_bitmap & (2**$i) ) {
 
1303
         MKDEBUG && _d('Param', $i, 'is NULL (bitmap)');
 
1304
         $val = 'NULL';
 
1305
         $len = 0;
 
1306
      }
 
1307
      else {
 
1308
         if ( $unpack_type{$types[$i]} ) {
 
1309
            ($val, $len) = $unpack_type{$types[$i]}->($data);
 
1310
         }
 
1311
         else {
 
1312
            # TODO: this is probably going to break parsing other param vals
 
1313
            MKDEBUG && _d('No handler for param', $i, 'type', $types[$i]);
 
1314
            $val = '?';
 
1315
            $len = 0;
 
1316
         }
 
1317
      }
 
1318
 
 
1319
      # Replace ? in prepared statement with value.
 
1320
      MKDEBUG && _d('Param', $i, 'val:', $val);
 
1321
      $arg =~ s/\?/$val/;
 
1322
 
 
1323
      # Remove this param val from the data, putting us at the next one.
 
1324
      substr($data, 0, $len * 2, '') if $len;
 
1325
   }
 
1326
 
 
1327
   my $pkt = {
 
1328
      sth_id => $sth_id,
 
1329
      arg    => "EXECUTE $arg",
 
1330
   };
 
1331
   MKDEBUG && _d('Execute packet:', Dumper($pkt));
 
1332
   return $pkt;
 
1333
}
 
1334
 
 
1335
sub get_sth_id {
 
1336
   my ( $data ) = @_;
 
1337
   return unless $data;
 
1338
   my $sth_id = to_num(substr($data, 2, 8));
 
1339
   return $sth_id;
 
1340
}
 
1341
 
 
1342
sub parse_flags {
 
1343
   my ( $flags ) = @_;
 
1344
   die "I need flags" unless $flags;
 
1345
   MKDEBUG && _d('Flag data:', $flags);
 
1346
   my %flags     = %flag_for;
 
1347
   my $flags_dec = to_num($flags);
 
1348
   foreach my $flag ( keys %flag_for ) {
 
1349
      my $flagno    = $flag_for{$flag};
 
1350
      $flags{$flag} = ($flags_dec & $flagno ? 1 : 0);
 
1351
   }
 
1352
   return \%flags;
 
1353
}
 
1354
 
 
1355
# Takes a scalarref to a hex string of compressed data.
 
1356
# Returns a scalarref to a hex string of the uncompressed data.
 
1357
# The given hex string of compressed data is not modified.
 
1358
sub uncompress_data {
 
1359
   my ( $data, $len ) = @_;
 
1360
   die "I need data" unless $data;
 
1361
   die "I need a len argument" unless $len;
 
1362
   die "I need a scalar reference to data" unless ref $data eq 'SCALAR';
 
1363
   MKDEBUG && _d('Uncompressing data');
 
1364
   our $InflateError;
 
1365
 
 
1366
   # Pack hex string into compressed binary data.
 
1367
   my $comp_bin_data = pack('H*', $$data);
 
1368
 
 
1369
   # Uncompress the compressed binary data.
 
1370
   my $uncomp_bin_data = '';
 
1371
   my $z = new IO::Uncompress::Inflate(
 
1372
      \$comp_bin_data
 
1373
   ) or die "IO::Uncompress::Inflate failed: $InflateError";
 
1374
   my $status = $z->read(\$uncomp_bin_data, $len)
 
1375
      or die "IO::Uncompress::Inflate failed: $InflateError";
 
1376
 
 
1377
   # Unpack the uncompressed binary data back into a hex string.
 
1378
   # This is the original MySQL packet(s).
 
1379
   my $uncomp_data = unpack('H*', $uncomp_bin_data);
 
1380
 
 
1381
   return \$uncomp_data;
 
1382
}
 
1383
 
 
1384
# Returns 1 on success or 0 on failure.  Failure is probably
 
1385
# detecting compression but not being able to uncompress
 
1386
# (uncompress_packet() returns 0).
 
1387
sub detect_compression {
 
1388
   my ( $self, $packet, $session ) = @_;
 
1389
   MKDEBUG && _d('Checking for client compression');
 
1390
   # This is a necessary hack for detecting compression in-stream without
 
1391
   # having seen the client handshake and CLIENT_COMPRESS flag.  If the
 
1392
   # client is compressing packets, there will be an extra 7 bytes before
 
1393
   # the regular MySQL header.  For short COM_QUERY commands, these 7 bytes
 
1394
   # are usually zero where we'd expect to see 03 for COM_QUERY.  So if we
 
1395
   # parse this packet and it looks like a COM_SLEEP (00) which is not a
 
1396
   # command that the client can send, then chances are the client is using
 
1397
   # compression.
 
1398
   my $com = parse_com_packet($packet->{data}, $packet->{mysql_data_len});
 
1399
   if ( $com && $com->{code} eq COM_SLEEP ) {
 
1400
      MKDEBUG && _d('Client is using compression');
 
1401
      $session->{compress} = 1;
 
1402
 
 
1403
      # Since parse_packet() didn't know the packet was compressed, it
 
1404
      # called remove_mysql_header() which removed the first 4 of 7 bytes
 
1405
      # of the compression header.  We must restore these 4 bytes, then
 
1406
      # uncompress and remove the MySQL header.  We only do this once.
 
1407
      $packet->{data} = $packet->{mysql_hdr} . $packet->{data};
 
1408
      return 0 unless $self->uncompress_packet($packet, $session);
 
1409
      remove_mysql_header($packet);
 
1410
   }
 
1411
   else {
 
1412
      MKDEBUG && _d('Client is NOT using compression');
 
1413
      $session->{compress} = 0;
 
1414
   }
 
1415
   return 1;
 
1416
}
 
1417
 
 
1418
# Returns 1 if the packet was uncompressed or 0 if we can't uncompress.
 
1419
# Failure is usually due to IO::Uncompress not being available.
 
1420
sub uncompress_packet {
 
1421
   my ( $self, $packet, $session ) = @_;
 
1422
   die "I need a packet"  unless $packet;
 
1423
   die "I need a session" unless $session;
 
1424
 
 
1425
   # From the doc: "A compressed packet header is:
 
1426
   #    packet length (3 bytes),
 
1427
   #    packet number (1 byte),
 
1428
   #    and Uncompressed Packet Length (3 bytes).
 
1429
   # The Uncompressed Packet Length is the number of bytes
 
1430
   # in the original, uncompressed packet. If this is zero
 
1431
   # then the data is not compressed."
 
1432
 
 
1433
   my $data;
 
1434
   my $comp_hdr;
 
1435
   my $comp_data_len;
 
1436
   my $pkt_num;
 
1437
   my $uncomp_data_len;
 
1438
   eval {
 
1439
      $data            = \$packet->{data};
 
1440
      $comp_hdr        = substr($$data, 0, 14, '');
 
1441
      $comp_data_len   = to_num(substr($comp_hdr, 0, 6));
 
1442
      $pkt_num         = to_num(substr($comp_hdr, 6, 2));
 
1443
      $uncomp_data_len = to_num(substr($comp_hdr, 8, 6));
 
1444
      MKDEBUG && _d('Compression header data:', $comp_hdr,
 
1445
         'compressed data len (bytes)', $comp_data_len,
 
1446
         'number', $pkt_num,
 
1447
         'uncompressed data len (bytes)', $uncomp_data_len);
 
1448
   };
 
1449
   if ( $EVAL_ERROR ) {
 
1450
      $session->{EVAL_ERROR} = $EVAL_ERROR;
 
1451
      $self->fail_session($session, 'failed to parse compression header');
 
1452
      return 0;
 
1453
   }
 
1454
 
 
1455
   if ( $uncomp_data_len ) {
 
1456
      eval {
 
1457
         $data = uncompress_data($data, $uncomp_data_len);
 
1458
         $packet->{data} = $$data;
 
1459
      };
 
1460
      if ( $EVAL_ERROR ) {
 
1461
         $session->{EVAL_ERROR} = $EVAL_ERROR;
 
1462
         $self->fail_session($session, 'failed to uncompress data');
 
1463
         die "Cannot uncompress packet.  Check that IO::Uncompress::Inflate "
 
1464
            . "is installed.\nError: $EVAL_ERROR";
 
1465
      }
 
1466
   }
 
1467
   else {
 
1468
      MKDEBUG && _d('Packet is not really compressed');
 
1469
      $packet->{data} = $$data;
 
1470
   }
 
1471
 
 
1472
   return 1;
 
1473
}
 
1474
 
 
1475
# Removes the first 4 bytes of the packet data which should be
 
1476
# a MySQL header: 3 bytes packet length, 1 byte packet number.
 
1477
sub remove_mysql_header {
 
1478
   my ( $packet ) = @_;
 
1479
   die "I need a packet" unless $packet;
 
1480
 
 
1481
   # NOTE: the data is modified by the inmost substr call here!  If we
 
1482
   # had all the data in the TCP packets, we could change this to a while
 
1483
   # loop; while get-a-packet-from-$data, do stuff, etc.  But we don't,
 
1484
   # and we don't want to either.
 
1485
   my $mysql_hdr      = substr($packet->{data}, 0, 8, '');
 
1486
   my $mysql_data_len = to_num(substr($mysql_hdr, 0, 6));
 
1487
   my $pkt_num        = to_num(substr($mysql_hdr, 6, 2));
 
1488
   MKDEBUG && _d('MySQL packet: header data', $mysql_hdr,
 
1489
      'data len (bytes)', $mysql_data_len, 'number', $pkt_num);
 
1490
 
 
1491
   $packet->{mysql_hdr}      = $mysql_hdr;
 
1492
   $packet->{mysql_data_len} = $mysql_data_len;
 
1493
   $packet->{number}         = $pkt_num;
 
1494
 
 
1495
   return;
 
1496
}
 
1497
 
 
1498
sub _get_errors_fh {
 
1499
   my ( $self ) = @_;
 
1500
   my $errors_fh = $self->{errors_fh};
 
1501
   return $errors_fh if $errors_fh;
 
1502
 
 
1503
   # Errors file isn't open yet; try to open it.
 
1504
   my $o = $self->{o};
 
1505
   if ( $o && $o->has('tcpdump-errors') && $o->got('tcpdump-errors') ) {
 
1506
      my $errors_file = $o->get('tcpdump-errors');
 
1507
      MKDEBUG && _d('tcpdump-errors file:', $errors_file);
 
1508
      open $errors_fh, '>>', $errors_file
 
1509
         or die "Cannot open tcpdump-errors file $errors_file: $OS_ERROR";
 
1510
   }
 
1511
 
 
1512
   $self->{errors_fh} = $errors_fh;
 
1513
   return $errors_fh;
 
1514
}
 
1515
 
 
1516
sub fail_session {
 
1517
   my ( $self, $session, $reason ) = @_;
 
1518
   MKDEBUG && _d('Client', $session->{client}, 'failed because', $reason);
 
1519
   my $errors_fh = $self->_get_errors_fh();
 
1520
   if ( $errors_fh ) {
 
1521
      my $raw_packets = $session->{raw_packets};
 
1522
      delete $session->{raw_packets};  # Don't dump, it's printed below.
 
1523
      $session->{reason_for_failure} = $reason;
 
1524
      my $session_dump = '# ' . Dumper($session);
 
1525
      chomp $session_dump;
 
1526
      $session_dump =~ s/\n/\n# /g;
 
1527
      print $errors_fh "$session_dump\n";
 
1528
      {
 
1529
         local $LIST_SEPARATOR = "\n";
 
1530
         print $errors_fh "@$raw_packets";
 
1531
         print $errors_fh "\n";
 
1532
      }
 
1533
   }
 
1534
   delete $self->{sessions}->{$session->{client}};
 
1535
   return;
 
1536
}
 
1537
 
 
1538
# Delete anything we added to the session related to
 
1539
# buffering a large query received in multiple packets.
 
1540
sub _delete_buff {
 
1541
   my ( $self, $session ) = @_;
 
1542
   map { delete $session->{$_} } qw(buff buff_left mysql_data_len);
 
1543
   return;
 
1544
}
 
1545
 
 
1546
sub _d {
 
1547
   my ($package, undef, $line) = caller 0;
 
1548
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
1549
        map { defined $_ ? $_ : 'undef' }
 
1550
        @_;
 
1551
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
1552
}
 
1553
 
 
1554
1;
 
1555
 
 
1556
# ###########################################################################
 
1557
# End MySQLProtocolParser package
 
1558
# ###########################################################################