/*
 * This file is distributed as part of MaxScale from SkySQL.  It is free
 * software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation,
 * version 2.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright SkySQL Ab 2014
 */

/**
 * @file modutil.c  - Implementation of useful routines for modules
 *
 * @verbatim
 * Revision History
 *
 * Date		Who		Description
 * 04/06/14	Mark Riddoch	Initial implementation
 *
 * @endverbatim
 */
#include <buffer.h>
#include <string.h>
#include <mysql_client_server_protocol.h>

/**
 * Check if a GWBUF structure is a MySQL COM_QUERY packet
 *
 * @param	buf	Buffer to check
 * @return	True if GWBUF is a COM_QUERY packet
 */
int
modutil_is_SQL(GWBUF *buf)
{
unsigned char	*ptr;

	if (GWBUF_LENGTH(buf) < 5)
		return 0;
	ptr = GWBUF_DATA(buf);
	return ptr[4] == 0x03;		// COM_QUERY
}

/**
 * Extract the SQL portion of a COM_QUERY packet
 *
 * NB This sets *sql to point into the packet and does not
 * allocate any new storage. The string pointed to by *sql is
 * not NULL terminated.
 *
 * This routine is very simplistic and does not deal with SQL text
 * that spans multiple buffers.
 *
 * The length returned is the complete length of the SQL, which may
 * be larger than the amount of data in this packet.
 *
 * @param	buf	The packet buffer
 * @param	sql	Pointer that is set to point at the SQL data
 * @param	length	Length of the SQL query data
 * @return	True if the packet is a COM_QUERY packet
 */
int
modutil_extract_SQL(GWBUF *buf, char **sql, int *length)
{
unsigned char	*ptr;

	if (!modutil_is_SQL(buf))
		return 0;
	ptr = GWBUF_DATA(buf);
	*length = *ptr++;
	*length += (*ptr++ << 8);
	*length += (*ptr++ << 16);
        ptr += 2;  // Skip sequence id	and COM_QUERY byte
	*length = *length - 1;
	*sql = (char *)ptr;
	return 1;
}

/**
 * Extract the SQL portion of a COM_QUERY packet
 *
 * NB This sets *sql to point into the packet and does not
 * allocate any new storage. The string pointed to by *sql is
 * not NULL terminated.
 *
 * The number of bytes pointed to *sql is returned in *length
 *
 * The remaining number of bytes required for the complete query string
 * are returned in *residual
 *
 * @param	buf		The packet buffer
 * @param	sql		Pointer that is set to point at the SQL data
 * @param	length		Length of the SQL query data pointed to by sql
 * @param	residual	Any remain part of the query in future packets
 * @return	True if the packet is a COM_QUERY packet
 */
int
modutil_MySQL_Query(GWBUF *buf, char **sql, int *length, int *residual)
{
unsigned char	*ptr;

	if (!modutil_is_SQL(buf))
		return 0;
	ptr = GWBUF_DATA(buf);
	*residual = *ptr++;
	*residual += (*ptr++ << 8);
	*residual += (*ptr++ << 16);
        ptr += 2;  // Skip sequence id	and COM_QUERY byte
	*residual = *residual - 1;
	*length = GWBUF_LENGTH(buf) - 5;
	*residual -= *length;
	*sql = (char *)ptr;
	return 1;
}



/**
 * Replace the contents of a GWBUF with the new SQL statement passed as a text string.
 * The routine takes care of the modification needed to the MySQL packet,
 * returning a GWBUF chain that can be used to send the data to a MySQL server
 *
 * @param orig	The original request in a GWBUF
 * @param sql	The SQL text to replace in the packet
 * @return A newly formed GWBUF containing the MySQL packet.
 */
GWBUF *
modutil_replace_SQL(GWBUF *orig, char *sql)
{
unsigned char	*ptr;
int	length, newlength;
GWBUF	*addition;

	if (!modutil_is_SQL(orig))
		return NULL;
	ptr = GWBUF_DATA(orig);
	length = *ptr++;
	length += (*ptr++ << 8);
	length += (*ptr++ << 16);
        ptr += 2;  // Skip sequence id	and COM_QUERY byte

	newlength = strlen(sql);
	if (length - 1 == newlength)
	{
		/* New SQL is the same length as old */
		memcpy(ptr, sql, newlength);
	}
	else if (length - 1 > newlength)
	{
		/* New SQL is shorter */
		memcpy(ptr, sql, newlength);
		GWBUF_RTRIM(orig, (length - 1) - newlength);
	}
	else
	{
		memcpy(ptr, sql, length - 1);
		addition = gwbuf_alloc(newlength - (length - 1));
		memcpy(GWBUF_DATA(addition), &sql[length - 1], newlength - (length - 1));
		ptr = GWBUF_DATA(orig);
		*ptr++ = (newlength + 1) & 0xff;
		*ptr++ = ((newlength + 1) >> 8) & 0xff;
		*ptr++ = ((newlength + 1) >> 16) & 0xff;
		orig->next = addition;
	}

	return orig;
}

/**
 * Copy query string from GWBUF buffer to separate memory area.
 * 
 * @param buf   GWBUF buffer including the query
 * 
 * @return Plain text query if the packet type is COM_QUERY. Otherwise return 
 * a string including the packet type.
 */
char *
modutil_get_query(GWBUF *buf)
{
        uint8_t*           packet;
        mysql_server_cmd_t packet_type;
        size_t             len;
        char*              query_str;
        
        packet = GWBUF_DATA(buf);
        packet_type = packet[4];
        
        switch (packet_type) {
                case MYSQL_COM_QUIT:
                        len = strlen("[Quit msg]")+1;
                        if ((query_str = (char *)malloc(len+1)) == NULL)
                        {
                                goto retblock;
                        }
                        memcpy(query_str, "[Quit msg]", len);
                        memset(&query_str[len], 0, 1);
                        break;
                        
                case MYSQL_COM_QUERY:
                        len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */        
                        if ((query_str = (char *)malloc(len+1)) == NULL)
                        {
                                goto retblock;
                        }
                        memcpy(query_str, &packet[5], len);
                        memset(&query_str[len], 0, 1);
                        break;
                        
                default:
                        len = strlen(STRPACKETTYPE(packet_type))+1;
                        if ((query_str = (char *)malloc(len+1)) == NULL)
                        {
                                goto retblock;
                        }
                        memcpy(query_str, STRPACKETTYPE(packet_type), len);
                        memset(&query_str[len], 0, 1);
                        break;
        } /*< switch */
retblock:
        return query_str;
}
