/* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.

   This program 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 of the License.

   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 St, Fifth Floor, Boston, MA 02110-1301  USA */

/**
  @file

  =============================================
   Client-side protocol tracing infrastructure
  =============================================

  If a plugin of type MYSQL_CLIENT_TRACE_PLUGIN is loaded into
  libmysql and its instance is pointed by the global trace_plugin
  pointer, this plugin is notified of various protocol trace events.
  See include/mysql/plugin_trace.h for documentation of trace plugin
  methods and the list of possible trace events.

  These trace events are generated with MYSQL_TRACE() macro put in
  relevant places in libmysql code. The macro calls mysql_trace_trace()
  function defined here. This function calls trace plugin's
  trace_event() method, if it is defined.

  For each traced connection, the state is kept in st_mysql_trace_info
  structure (see mysql_trace.h).

  To correctly interpret each trace event, trace plugin is informed
  of the current protocol stage (see include/mysql/plugin_trace.h for
  list of stages). The current protocol stage is part of the
  connection tracing state. It is updated with MYSQL_TRACE_STAGE()
  hooks within libmysql code.
*/

#include <my_global.h>
#include "mysql.h"
#include "mysql_trace.h"

/*
  Definition of the global trace_plugin pointer - see plugin_trace.h
  for declaration and description.
*/
struct st_mysql_client_plugin_TRACE *trace_plugin= NULL;

/*
  Macros for manipulating trace_info structure.
*/
#define GET_DATA(TI)      (TI)->trace_plugin_data
#define SET_DATA(TI,D)    GET_DATA(TI) = (D)
#define GET_STAGE(TI)     (TI)->stage
#define TEST_STAGE(TI,X)  (GET_STAGE(TI) == PROTOCOL_STAGE_ ## X)
#define SET_STAGE(TI,X)   GET_STAGE(TI) = PROTOCOL_STAGE_ ## X


/**
  Initialize tracing in a given connection.

  This function is called from MYSQL_TRACE_STAGE() when the initial
  CONNECTING stage is reported. It allocates and initializes trace_info
  structure which is then attached to the connection handle.
*/

void mysql_trace_start(struct st_mysql *m)
{
  struct st_mysql_trace_info *trace_info;

  trace_info= my_malloc(PSI_NOT_INSTRUMENTED,
                        sizeof(struct st_mysql_trace_info),
                        MYF(MY_ZEROFILL));
  if (!trace_info)
  {
    /*
      Note: in this case trace_data of the connection will
      remain NULL and thus tracing will be disabled.
    */
    return;
  }

  /*
    This function should be called only when a trace plugin
    is loaded and thus trace_plugin pointer is not NULL. This
    is handled in MYSQL_TRACE_STAGE() macro (mysql_trace.h).
  */
  DBUG_ASSERT(trace_plugin);

  trace_info->plugin= trace_plugin;
  trace_info->stage=  PROTOCOL_STAGE_CONNECTING;

  /*
    Call plugin's tracing_start() method, if defined.
  */

  if (trace_info->plugin->tracing_start)
  {
    trace_info->trace_plugin_data=
      trace_info->plugin->tracing_start(
        trace_info->plugin,
        m, PROTOCOL_STAGE_CONNECTING);
  }
  else
  {
    trace_info->trace_plugin_data= NULL;
  }

  /* Store trace_info in the connection handle. */

  TRACE_DATA(m)= trace_info;
}


/**
  Report a protocol trace event to trace plugin.

  Calls plugin's trace_event() method, if it is defined, passing to
  it the event, the current protocol stage and event arguments (if any).

  Terminates tracing of the connection when appropriate.

  @param m        MYSQL connection handle
  @param ev       trace event to be reported
  @param args     trace event arguments
*/

void mysql_trace_trace(struct st_mysql  *m,
                       enum trace_event ev,
                       struct st_trace_event_args args)
{
  struct st_mysql_trace_info *trace_info= TRACE_DATA(m);
  struct st_mysql_client_plugin_TRACE *plugin= trace_info ? trace_info->plugin : NULL;
  int    quit_tracing= 0;

  /*
    If trace_info is NULL then this connection is not traced and this
    function should not be called - this is handled inside MYSQL_TRACE()
    macro.
  */
  DBUG_ASSERT(trace_info);

  /* Call plugin's trace_event() method if defined */

  if (plugin->trace_event)
  {
    /*
      Temporarily disable tracing while executing plugin's method
      by setting trace data pointer to NULL. Also, set reconnect
      flag to 0 in case plugin executes any queries.
    */
    my_bool saved_reconnect_flag= m->reconnect;

    TRACE_DATA(m)= NULL;
    m->reconnect=  0;
    quit_tracing= plugin->trace_event(plugin, GET_DATA(trace_info), m,
                                      GET_STAGE(trace_info), ev, args);
    m->reconnect= saved_reconnect_flag;
    TRACE_DATA(m)= trace_info;
  }

  /* Stop tracing if requested or end of connection. */

  if (quit_tracing
      || TEST_STAGE(trace_info, DISCONNECTED)
      || TRACE_EVENT_DISCONNECTED == ev)
  {
    /* Note: this disables further tracing */
    TRACE_DATA(m)= NULL;

    if (plugin->tracing_stop)
      plugin->tracing_stop(plugin, m, GET_DATA(trace_info));

    my_free(trace_info);
  }
}


#ifndef DBUG_OFF
/*
  These functions are declared in plugin_trace.h.

  Consult documentation of *_LIST() macros (plugin_trace.h) to see how
  switch() bodies are constructed with the *_get_name() macros.
*/

#define protocol_stage_get_name(X) case PROTOCOL_STAGE_ ## X: return #X;

const char* protocol_stage_name(enum protocol_stage stage)
{
  switch(stage)
  {
  PROTOCOL_STAGE_LIST(get_name)
  default: return "<unknown stage>";
  }
}


#define trace_event_get_name(X) case TRACE_EVENT_ ## X: return #X;

const char* trace_event_name(enum trace_event ev)
{
  switch(ev)
  {
  TRACE_EVENT_LIST(get_name)
  default: return "<unknown event>";
  }
}

#endif
