📄 mysqlnd_wireprotocol.c
字号:
/* +----------------------------------------------------------------------+ | PHP Version 6 | +----------------------------------------------------------------------+ | Copyright (c) 2006-2007 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Georg Richter <georg@mysql.com> | | Andrey Hristov <andrey@mysql.com> | | Ulf Wendel <uwendel@mysql.com> | +----------------------------------------------------------------------+*/#include "php.h"#include "php_globals.h"#include "mysqlnd.h"#include "mysqlnd_priv.h"#include "mysqlnd_wireprotocol.h"#include "mysqlnd_statistics.h"#include "mysqlnd_palloc.h"#include "ext/standard/sha1.h"#include "php_network.h"#ifndef PHP_WIN32#include <netinet/tcp.h>#else#endif#define USE_CORK 0#define MYSQLND_SILENT#define MYSQLND_DUMP_HEADER_N_BODY2#define MYSQLND_DUMP_HEADER_N_BODY_FULL2#define MYSQLND_MAX_PACKET_SIZE (256L*256L*256L-1)#define PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_size, packet_type) \ { \ if (FAIL == mysqlnd_read_header((conn), &((packet)->header) TSRMLS_CC)) {\ return FAIL;\ }\ if ((buf_size) < (packet)->header.size) { \ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Packet buffer wasn't big enough" \ "%u bytes will be unread", (packet)->header.size - (buf_size));\ }\ if (!mysqlnd_read_body((conn), (buf), \ MIN((buf_size), (packet)->header.size) TSRMLS_CC)) { \ php_error(E_WARNING, "Empty %s packet body", (packet_type));\ return FAIL; \ } \ }extern mysqlnd_packet_methods packet_methods[];static const char *unknown_sqlstate= "HY000";/* {{{ mysqlnd_command_to_text */const char * const mysqlnd_command_to_text[COM_END] ={ "SLEEP", "QUIT", "INIT_DB", "QUERY", "FIELD_LIST", "CREATE_DB", "DROP_DB", "REFRESH", "SHUTDOWN", "STATISTICS", "PROCESS_INFO", "CONNECT", "PROCESS_KILL", "DEBUG", "PING", "TIME", "DELAYED_INSERT", "CHANGE_USER", "BINLOG_DUMP", "TABLE_DUMP", "CONNECT_OUT", "REGISTER_SLAVE", "STMT_PREPARE", "STMT_EXECUTE", "STMT_SEND_LONG_DATA", "STMT_CLOSE", "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON"};/* }}} *//* {{{ php_mysqlnd_net_field_length Get next field's length */unsigned long php_mysqlnd_net_field_length(zend_uchar **packet){ register zend_uchar *p= (zend_uchar *)*packet; if (*p < 251) { (*packet)++; return (unsigned long) *p; } switch (*p) { case 251: (*packet)++; return MYSQLND_NULL_LENGTH; case 252: (*packet) += 3; return (unsigned long) uint2korr(p+1); case 253: (*packet) += 4; return (unsigned long) uint3korr(p+1); default: (*packet) += 9; return (unsigned long) uint4korr(p+1); }}/* }}} *//* {{{ php_mysqlnd_net_field_length_ll Get next field's length */mynd_ulonglong php_mysqlnd_net_field_length_ll(zend_uchar **packet){ register zend_uchar *p= (zend_uchar *)*packet; if (*p < 251) { (*packet)++; return (mynd_ulonglong) *p; } switch (*p) { case 251: (*packet)++; return (mynd_ulonglong) MYSQLND_NULL_LENGTH; case 252: (*packet) += 3; return (mynd_ulonglong) uint2korr(p + 1); case 253: (*packet) += 4; return (mynd_ulonglong) uint3korr(p + 1); default: (*packet) += 9; return (mynd_ulonglong) uint8korr(p + 1); }}/* }}} *//* {{{ php_mysqlnd_net_store_length */zend_uchar *php_mysqlnd_net_store_length(zend_uchar *packet, mynd_ulonglong length){ if (length < (mynd_ulonglong) L64(251)) { *packet = (zend_uchar) length; return packet + 1; } if (length < (mynd_ulonglong) L64(65536)) { *packet++ = 252; int2store(packet,(uint) length); return packet + 2; } if (length < (mynd_ulonglong) L64(16777216)) { *packet++ = 253; int3store(packet,(ulong) length); return packet + 3; } *packet++ = 254; int8store(packet, length); return packet + 8;}/* }}} *//* {{{ php_mysqlnd_consume_uneaten_data */size_t php_mysqlnd_consume_uneaten_data(MYSQLND * const conn, enum php_mysqlnd_server_command cmd TSRMLS_DC){ /* Switch to non-blocking mode and try to consume something from the line, if possible, then continue. This saves us from looking for the actuall place where out-of-order packets have been sent. If someone is completely sure that everything is fine, he can switch it off. */ char tmp_buf[256]; MYSQLND_NET *net = &conn->net; size_t skipped_bytes = 0; int opt = PHP_STREAM_OPTION_BLOCKING; int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL TSRMLS_CC); if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) { /* Do a read of 1 byte */ int bytes_consumed; do { skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf, sizeof(tmp_buf))); } while (bytes_consumed == sizeof(tmp_buf)); if (was_blocked) { net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC); } if (bytes_consumed) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't " "consumed all the output from the server", bytes_consumed, mysqlnd_command_to_text[net->last_command]); } } net->last_command = cmd; return skipped_bytes;}/* }}} *//* {{{ php_mysqlnd_read_error_from_line */staticenum_func_status php_mysqlnd_read_error_from_line(zend_uchar *buf, size_t buf_len, char *error, int error_buf_len, unsigned int *error_no, char *sqlstate){ zend_uchar *p = buf; int error_msg_len= 0; if (buf_len > 2) { *error_no = uint2korr(p); p+= 2; /* sqlstate is following */ if (*p == '#') { memcpy(sqlstate, ++p, MYSQLND_SQLSTATE_LENGTH); p+= MYSQLND_SQLSTATE_LENGTH; } error_msg_len = buf_len - (p - buf); error_msg_len = MIN(error_msg_len, error_buf_len - 1); memcpy(error, p, error_msg_len); } else { *error_no = CR_UNKNOWN_ERROR; memcpy(sqlstate, unknown_sqlstate, MYSQLND_SQLSTATE_LENGTH); } sqlstate[MYSQLND_SQLSTATE_LENGTH] = '\0'; error[error_msg_len]= '\0'; return FAIL;} /* }}} *//* {{{ mysqlnd_set_sock_no_delay */int mysqlnd_set_sock_no_delay(php_stream *stream){ int socketd = ((php_netstream_data_t*)stream->abstract)->socket; int ret = SUCCESS; int flag = 1; int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); if (result == -1) { ret = FAILURE; } return ret;}/* }}} *//* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */#define STORE_HEADER_SIZE(safe_storage, buffer) int4store((safe_storage), (*(uint32 *)(buffer)))#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))/* {{{ mysqlnd_stream_write_w_header *//* IMPORTANT : It's expected that buf has place in the beginning for MYSQLND_HEADER_SIZE !!!! This is done for performance reasons in the caller of this function. Otherwise we will have to do send two TCP packets, or do new alloc and memcpy. Neither are quick, thus the clients of this function are obligated to do what they are asked for. `count` is actually the length of the payload data. Thus : count + MYSQLND_HEADER_SIZE = sizeof(buf) (not the pointer but the actual buffer)*/size_t mysqlnd_stream_write_w_header(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC){ zend_uchar safe_storage[MYSQLND_HEADER_SIZE]; MYSQLND_NET *net = &conn->net; size_t old_chunk_size = net->stream->chunk_size; size_t ret, left = count, packets_sent = 1; zend_uchar *p = (zend_uchar *) buf; net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE; while (left > MYSQLND_MAX_PACKET_SIZE) { STORE_HEADER_SIZE(safe_storage, p); int3store(p, MYSQLND_MAX_PACKET_SIZE); int1store(p + 3, net->packet_no); net->packet_no++; ret = php_stream_write(net->stream, (char *)p, MYSQLND_MAX_PACKET_SIZE + MYSQLND_HEADER_SIZE); RESTORE_HEADER_SIZE(p, safe_storage); p += MYSQLND_MAX_PACKET_SIZE; left -= MYSQLND_MAX_PACKET_SIZE; packets_sent++; } /* Even for zero size payload we have to send a packet */ STORE_HEADER_SIZE(safe_storage, p); int3store(p, left); int1store(p + 3, net->packet_no); net->packet_no++; ret = php_stream_write(net->stream, (char *)p, left + MYSQLND_HEADER_SIZE); RESTORE_HEADER_SIZE(p, safe_storage); MYSQLND_INC_CONN_STATISTIC_W_VALUE3(&conn->stats, STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE, STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE, STAT_PACKETS_SENT, packets_sent); net->stream->chunk_size = old_chunk_size; return ret;}/* }}} *//* {{{ mysqlnd_stream_write_w_command */#if USE_CORK && defined(TCP_CORK)staticsize_t mysqlnd_stream_write_w_command(MYSQLND * const conn, enum php_mysqlnd_server_command command, const char * const buf, size_t count TSRMLS_DC){ zend_uchar safe_storage[MYSQLND_HEADER_SIZE + 1]; MYSQLND_NET *net = &conn->net; size_t old_chunk_size = net->stream->chunk_size; size_t ret, left = count, header_len = MYSQLND_HEADER_SIZE + 1, packets_sent = 1; const zend_uchar *p = (zend_uchar *) buf; zend_bool command_sent = FALSE; int corked = 1; net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE; setsockopt(((php_netstream_data_t*)net->stream->abstract)->socket, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); int1store(safe_storage + MYSQLND_HEADER_SIZE, command); while (left > MYSQLND_MAX_PACKET_SIZE) { size_t body_size = MYSQLND_MAX_PACKET_SIZE; int3store(safe_storage, MYSQLND_MAX_PACKET_SIZE); int1store(safe_storage + 3, net->packet_no); net->packet_no++; ret = php_stream_write(net->stream, (char *)safe_storage, header_len); if (command_sent == FALSE) { --header_len; /* Sent one byte less*/ --body_size; command_sent = TRUE; } ret = php_stream_write(net->stream, (char *)p, body_size); p += body_size; left -= body_size; packets_sent++; } /* Even for zero size payload we have to send a packet */ int3store(safe_storage, header_len == MYSQLND_HEADER_SIZE? left:left+1); int1store(safe_storage + 3, net->packet_no); net->packet_no++; ret = php_stream_write(net->stream, (char *)safe_storage, header_len); if (left) { ret = php_stream_write(net->stream, (char *)p, left); } corked = 0; setsockopt(((php_netstream_data_t*)net->stream->abstract)->socket, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); MYSQLND_INC_CONN_STATISTIC_W_VALUE3(&conn->stats, STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE); STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE); STAT_PACKETS_SENT, packets_sent); net->stream->chunk_size = old_chunk_size; return ret;}#endif/* }}} *//* {{{ mysqlnd_read_header */static enum_func_statusmysqlnd_read_header(MYSQLND *conn, mysqlnd_packet_header *header TSRMLS_DC){ MYSQLND_NET *net = &conn->net; char buffer[MYSQLND_HEADER_SIZE]; char *p = buffer; int to_read = MYSQLND_HEADER_SIZE, ret; do { if (!(ret= php_stream_read(net->stream, p, to_read))) { php_error(E_WARNING, "Error while reading header from socket"); return FAIL; } p += ret; to_read -= ret; } while (to_read); header->size = uint3korr(buffer); header->packet_no = uint1korr(buffer + 3); MYSQLND_INC_CONN_STATISTIC_W_VALUE3(&conn->stats, STAT_BYTES_RECEIVED, MYSQLND_HEADER_SIZE, STAT_PROTOCOL_OVERHEAD_IN, MYSQLND_HEADER_SIZE, STAT_PACKETS_RECEIVED, 1); if (net->packet_no == header->packet_no) { /* Have to increase the number, so we can send correct number back. It will round at 255 as this is unsigned char. The server needs this for simple flow control checking. */ net->packet_no++;#ifdef MYSQLND_DUMP_HEADER_N_BODY php_printf("HEADER: packet_no=%d size=%3d\n", header->packet_no, header->size);#endif return PASS; } php_error(E_WARNING, "Packets out of order. Expected %d received %d. Packet size=%d", net->packet_no, header->packet_no, header->size); return FAIL;}/* }}} *//* {{{ mysqlnd_read_body */staticsize_t mysqlnd_read_body(MYSQLND *conn, zend_uchar *buf, size_t size TSRMLS_DC){ size_t ret; char *p = (char *)buf; int i, iter = 0; MYSQLND_NET *net = &conn->net; do { size -= (ret = php_stream_read(net->stream, p, size));#ifdef MYSQLND_DUMP_HEADER_N_BODY if (size || iter++) { php_printf("read=%d buf=%p p=%p chunk_size=%d left=%d\n", ret, buf, p , net->stream->chunk_size, size); }#endif p += ret; } while (size > 0); MYSQLND_INC_CONN_STATISTIC_W_VALUE(&conn->stats, STAT_BYTES_RECEIVED, p - (char*)buf);#ifdef MYSQLND_DUMP_HEADER_N_BODY_FULL php_printf("\tBODY: requested=%d last_read=%3d\n\t", p - (char*)buf, ret); for (i = 0 ; i < p - (char*)buf; i++) printf("%c-", *(char *)(&(buf[i]))); php_printf("\n\t"); for (i = 0 ; i < p - (char*)buf; i++) printf("%.2X ", (int)*((char*)&(buf[i]))); php_printf("\n");#endif return p - (char*)buf;}/* }}} *//* {{{ php_mysqlnd_greet_read */static enum_func_statusphp_mysqlnd_greet_read(void *_packet, MYSQLND *conn TSRMLS_DC){ zend_uchar buf[256]; register zend_uchar *p= buf; register php_mysql_packet_greet *packet= (php_mysql_packet_greet *) _packet; PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "greeting"); packet->protocol_version = uint1korr(p); p++; if (packet->protocol_version == 0xFF) { php_mysqlnd_read_error_from_line(p, packet->header.size - 1, packet->error, sizeof(packet->error), &packet->error_no, packet->sqlstate); /* The server doesn't send sqlstate in the greet packet. It's a bug#26426 , so we have to set it correctly ourselves. It's probably "Too many connections, which has SQL state 08004". */ if (packet->error_no == 1040) { memcpy(packet->sqlstate, "08004", MYSQLND_SQLSTATE_LENGTH); } return PASS; } packet->server_version = pestrdup((char *)p, conn->persistent); p+= strlen(packet->server_version) + 1; /* eat the '\0' */ packet->thread_id = uint4korr(p); p+=4; memcpy(packet->scramble_buf, p, SCRAMBLE_LENGTH_323); p+= 8; /* pad1 */ p++; packet->server_capabilities = uint2korr(p); p+= 2; packet->charset_no = uint1korr(p); p++; packet->server_status = uint2korr(p); p+= 2; /* pad2 */ p+= 13; if (p - buf < packet->header.size) { /* scramble_buf is split into two parts */ memcpy(packet->scramble_buf + SCRAMBLE_LENGTH_323, p, SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323); } else { packet->pre41 = TRUE; } return PASS;}/* }}} *//* {{{ php_mysqlnd_greet_free_mem */staticvoid php_mysqlnd_greet_free_mem(void *_packet, zend_bool alloca){ php_mysql_packet_greet *p= (php_mysql_packet_greet *) _packet; if (p->server_version) { efree(p->server_version); p->server_version = NULL; } if (!alloca) { efree(p); }}/* }}} */#define MYSQLND_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | \ CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | \ CLIENT_MULTI_RESULTS)/* {{{ php_mysqlnd_crypt */staticvoid php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -