/**
 * @file trace_interpreter.cc
 *
 * TraceInterpreter - Class for interpreting a CAPI trace file.
 *
 * Copyright: Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: trace_interpreter.cc,v 1.1.2.1 2006/02/28 21:35:42 thomas Exp $
 * Project  CAPI for BSD
 * Target   capitrace - Tracing CAPI calls and messages
 * @date    21.09.2002
 * @author  "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

// System includes
#include <time.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include <stdio.h>
#include <errno.h>
#include <capi20.h>
#include <capi_bsdtrc.h>

// Import includes

#define __TRACE_INTERPRETER__

// Local includes
#ifndef __TRACE_FILE_HEADER_H
#  include "trace_file_header.h"
#endif
#ifndef __TRACE_INTERPRETER_H
#  include "trace_interpreter.h"
#endif





// === Private definitions ===============================================





/**
 * Stream output function for a struct timeval.
 */
static std::ostream & operator<<
   (std::ostream         &os,
    const struct timeval &tv);



class CYesNo;

/**
 * Stream output function for a CYesNo class.
 */
 
static std::ostream & operator<<
   (std::ostream &os,
    const CYesNo &yn);



/**
 * Support class to print out a textual representation of a bool value.
 */
class CYesNo
{
   public:
      /**
       * Constructor.
       */
      CYesNo (bool f)
         {
            m_f = f;
         }
   
      bool m_f;
      
}; // CYesNo





// === Definitions for public declarations ===============================





// === Prototypes of private functions ===================================





// === Implementation of class members ===================================





// --- Class for CAPI trace interpretation -------------------------------

/**
 * Constructor.
 */

CTraceInterpreter::CTraceInterpreter (void):
   m_oTraceMsgPrint (m_osOut)
{
   m_pisIn           = NULL;
   m_nTotalBytesRead = 0;
   
   m_fOutStreamOpened = false;
   
   m_fIncludeDataB3Msgs   = true;
   m_fIncludeDataB3Blocks = true;
   m_nDataB3MaxBlockLen   = UINT_MAX;
   m_fHexOutput           = true;
   
   m_pacMsgBuf   = NULL;
   m_nLenMsgBuf  = 0;
   m_paucDataBuf  = NULL;
   m_nLenDataBuf = 0;
   
} // CTraceInterpreter::CTraceInterpreter





/**
 * Destructor.
 */

CTraceInterpreter::~CTraceInterpreter (void)
{
   // if necessary close the output file
   CloseDestinationFile ();
   
   // if necessary close the input file
   CloseSourceFile ();
   
} // CTraceInterpreter::~CTraceInterpreter





/**
 * Set the input file name for the next interpretation run.
 */

void CTraceInterpreter::SetInputFile
   (const std::string &strFileName)
{
   m_strInputFileName = strFileName;
   
} // CTraceInterpreter::SetInputFile





/**
 * Set the output file name for the next interpretation run.
 */

void CTraceInterpreter::SetOutputFile
   (const std::string &strFileName)
{
   m_strOutputFileName = strFileName;
   
} // CTraceInterpreter::SetOutputFile





/**
 * Set configuration for handling Data-B3 messages.
 */

void CTraceInterpreter::SetDataB3MsgOutput
   (bool   fDataB3MsgOutput,
    bool   fDataB3BlockOutput,
    size_t nDataB3MaxBlockLen)
{
   m_fIncludeDataB3Msgs   = fDataB3MsgOutput;
   m_fIncludeDataB3Blocks = fDataB3BlockOutput;
   m_nDataB3MaxBlockLen   = nDataB3MaxBlockLen;
   
} // CTraceInterpreter::SetDataB3MsgOutput





/**
 * Set flag for optional hexadecimal output of the message.
 */

void CTraceInterpreter::SetHexOutput
   (bool f)
{
   m_fHexOutput = f;
   
} // CTraceInterpreter::SetHexOutput





/**
 * Perform interpretation of the current input file.
 */

void CTraceInterpreter::Run (void)
{
   CConfig::TracerConfig_t traceConfig;
   struct timeval          tvStartTime;
   
   // open the trace file and read the configuration used to create the trace
   OpenSourceFile (traceConfig, tvStartTime);
   
   // open the destination file or standard output according the configuration
   // and write the configuration to the file
   OpenDestinationFile (traceConfig, tvStartTime);
   
   // now read the source file until end-of-file
   while (ReadTraceBlock ())
   {
      // write an interpreted version of the trace block just read
      WriteTraceBlock ();
   }
   
   // close the output file
   CloseDestinationFile ();
   
   // close the input file
   CloseSourceFile ();
   
} // CTraceInterpreter::Run





/**
 * Open the source file.
 */

void CTraceInterpreter::OpenSourceFile
   (CConfig::TracerConfig_t &traceConfig,
    struct timeval          &tvStartTime)
{
   if (! m_pisIn)
   {
      if (m_strInputFileName == "" || m_strInputFileName == "-")
      {
         m_pisIn = &(std::cin);
      }
      else
      {
         m_isInFile.open (m_strInputFileName.c_str (),
                          std::ios::in | std::ios::binary);
         if (! m_isInFile.is_open ())
         {
            std::ostringstream oss;
            oss << "Error opening input file \"" << m_strInputFileName
                << "\": " << strerror (errno) << " (" << errno << ")";
            throw std::runtime_error (oss.str ().c_str ());
         }
         m_pisIn = &m_isInFile;
      }
   }
   
   m_nTotalBytesRead = 0;
   
   TraceFileHeader_t fh;
   m_pisIn->read ((char *) &fh, sizeof (fh));
   if (m_pisIn->fail () || m_pisIn->eof () || m_pisIn->gcount () != sizeof (fh))
   {
      throw std::runtime_error ("Error reading file header from input file");
   }
   if (C_GET_DWORD (fh.dwMagic) != TRACE_FILE_HEADER_MAGIC)
   {
      throw std::runtime_error ("Invalid header data in input file");
   }
   m_nTotalBytesRead += m_pisIn->gcount ();
   traceConfig.uCtlrNum             = C_GET_DWORD (fh.dwCtlrNum);
   traceConfig.fIsCtlrTracer        = (traceConfig.uCtlrNum != 0);
   traceConfig.fIncludeFctCalls     = C_GET_BYTE (fh.bIncludeFctCalls);
   traceConfig.fIncludeMessages     = C_GET_BYTE (fh.bIncludeMessages);
   traceConfig.fIncludeDataB3Msgs   = C_GET_BYTE (fh.bIncludeDataB3Msgs);
   traceConfig.fIncludeDataB3Blocks = C_GET_BYTE (fh.bIncludeDataB3Blocks);
   traceConfig.nDataB3BlockLen      = C_GET_WORD (fh.wDataB3BlockLen);
   traceConfig.strTraceFile         = m_strInputFileName;
   tvStartTime.tv_sec  = C_GET_DWORD (fh.dwTimeSeconds);
   tvStartTime.tv_usec = C_GET_DWORD (fh.dwTimeMicroSec);
   
} // CTraceInterpreter::OpenSourceFile





/**
 * Close the source file.
 */

void CTraceInterpreter::CloseSourceFile (void)
{
   if (m_pisIn)
   {
      m_pisIn = NULL;
      if (m_isInFile.is_open ())
      {
         m_isInFile.close ();
      }
   }
   
} // CTraceInterpreter::CloseSourceFile





/**
 * Open the destination file.
 */

void CTraceInterpreter::OpenDestinationFile
   (CConfig::TracerConfig_t &traceConfig,
    struct timeval          &tsStartTime)
{
   if (! m_fOutStreamOpened)
   {
      m_osOut.open (m_strOutputFileName.c_str ());

      std::ios::fmtflags f = m_osOut.flags ();
      m_osOut << std::setfill ('=') << std::setw (60) << "" << std::setfill (' ')
              << newl;
      if (traceConfig.fIsCtlrTracer)
      {
         m_osOut << "Controller trace for controller " << traceConfig.uCtlrNum
                 << " started at " << tsStartTime << newl;
      }
      else
      {
         m_osOut << "Application trace started at " << tsStartTime
                 << newl;
      }
      m_osOut.setf (std::ios::left, std::ios::adjustfield);
      m_osOut << std::setw (28) << "Include function calls:"
              << CYesNo (traceConfig.fIncludeFctCalls) << newl;
      m_osOut << std::setw (28) << "Include messages:"
              << CYesNo (traceConfig.fIncludeMessages) << newl;
      m_osOut << std::setw (28) << "Include Data-B3 messages:"
              << CYesNo (traceConfig.fIncludeDataB3Msgs) << newl;
      m_osOut << std::setw (28) << "Include Data-B3 blocks:"
              << CYesNo (traceConfig.fIncludeDataB3Blocks) << newl;
      m_osOut << std::setw (28) << "Maximum data block length:"
              << traceConfig.nDataB3BlockLen << newl;
      m_osOut << std::setfill ('-') << std::setw (60) << ""
              << std::setfill (' ') << newl;
      m_osOut.flags (f);
      
      m_fOutStreamOpened = true;
   }

} // CTraceInterpreter::OpenDestinationFile





/**
 * Close the destination file.
 */

void CTraceInterpreter::CloseDestinationFile (void)
{
   if (m_fOutStreamOpened)
   {
      m_osOut << std::setfill ('=') << std::setw (60) << "" << newl;

      m_osOut.close ();
      
      m_fOutStreamOpened = false;
   }
      
} // CTraceInterpreter::CloseDestinationFile





/**
 * Read a complete message and possible data block from the source.
 */

bool CTraceInterpreter::ReadTraceBlock (void)
{
   CAPITraceMsg_t *pTraceMsg;
   size_t          nLen;
   u_int32_t       dwLow;
   u_int32_t       dwHigh;
   
   // be sure there is at least enough memory to hold a basic trace message
   Reserve (m_pacMsgBuf, m_nLenMsgBuf, sizeof (*pTraceMsg));
   pTraceMsg = (CAPITraceMsg_t *) m_pacMsgBuf;
   
   // first read the length of the next message
   m_pisIn->read ((char *) &(pTraceMsg->head),
                  sizeof (pTraceMsg->head.wLength));
   if (m_pisIn->eof () && m_pisIn->gcount() == 0)
   {
      // reached regular end-of-file between two messages
      return (false);
   }
   m_nTotalBytesRead += m_pisIn->gcount ();
   if (m_pisIn->fail () ||
       m_pisIn->gcount () != sizeof (pTraceMsg->head.wLength))
   {
      std::ostringstream oss;
      oss << "Error reading block length from input at file position "
          << m_nTotalBytesRead;
      throw std::runtime_error (oss.str ().c_str ());
   }
   nLen = C_GET_WORD (pTraceMsg->head.wLength);
   if (nLen < sizeof (pTraceMsg->head) ||
       nLen > sizeof (pTraceMsg->head) +
                 sizeof (CAPITraceApplMsg_t) +
                 sizeof (CAPIMsg_t))
   {
      std::ostringstream oss;
      oss << "Read invalid message length " << nLen << " at file position "
          << m_nTotalBytesRead;
      throw std::runtime_error (oss.str ().c_str ());
   }
   
   // be sure there is enough buffer memory for the following message bytes,
   // preserving the bytes just read from the stream
   Reserve (m_pacMsgBuf, m_nLenMsgBuf, nLen, sizeof (pTraceMsg->head.wLength));
   pTraceMsg = (CAPITraceMsg_t *) m_pacMsgBuf;
   
   // read the rest of the message
   m_pisIn->read ((char *) &(pTraceMsg->head) +
                     sizeof (pTraceMsg->head.wLength),
                  nLen - sizeof (pTraceMsg->head.wLength));
   m_nTotalBytesRead += m_pisIn->gcount ();
   if (m_pisIn->fail () ||
       (size_t) m_pisIn->gcount () != nLen - sizeof (pTraceMsg->head.wLength))
   {
      std::ostringstream oss;
      oss << "Error reading trace message, only read "
          << m_pisIn->gcount () + sizeof (pTraceMsg->head.wLength) << " of "
          << nLen << " bytes at file position " << m_nTotalBytesRead;
      throw std::runtime_error (oss.str ().c_str ());
   }
   
   // if the trace message read contains a CAPI message (application or driver
   // layer) there may be an attached data block following now in the input
   // stream --> read it
   if (C_GET_WORD (pTraceMsg->head.wMsgType) == C4BTRC_MSGTYPE_APPL_MSG &&
       nLen >= sizeof (pTraceMsg->head) + sizeof (pTraceMsg->info.appl_msg))
   {
      nLen = C_GET_DWORD (pTraceMsg->info.appl_msg.dwDataLength);
   }
   else if (C_GET_WORD (pTraceMsg->head.wMsgType) == C4BTRC_MSGTYPE_DRVR_MSG &&
            nLen >= sizeof (pTraceMsg->head) + sizeof (pTraceMsg->info.drvr_msg))
   {
      nLen = C_GET_DWORD (pTraceMsg->info.drvr_msg.dwDataLength);
   }
   else
   {
      nLen = 0;
   }
   if (nLen > 0)
   {
      Reserve (m_paucDataBuf, m_nLenDataBuf, nLen);
      m_pisIn->read ((char *) m_paucDataBuf, nLen);
      m_nTotalBytesRead += m_pisIn->gcount ();
      if (m_pisIn->fail () || (size_t) m_pisIn->gcount () != nLen)
      {
         std::ostringstream oss;
         oss << "Error reading data block, only read " << m_pisIn->gcount ()
             << " of " << nLen << " bytes at file position "
             << m_nTotalBytesRead;
         throw std::runtime_error (oss.str ().c_str ());
      }
      
      // to enable the trace message translator to read the data block, we must
      // manipulate the data block address dword in the trace message to point
      // to the just filled data buffer
      dwLow  = C_LOW_PTR_DWORD (m_paucDataBuf);
      dwHigh = C_HIGH_PTR_DWORD (m_paucDataBuf);
      if (C_GET_WORD (pTraceMsg->head.wMsgType) == C4BTRC_MSGTYPE_APPL_MSG)
      {
         C_PUT_DWORD (pTraceMsg->info.appl_msg.dwDataPointerLow, dwLow);
         C_PUT_DWORD (pTraceMsg->info.appl_msg.dwDataPointerHigh, dwHigh);
      }
      else
      {
         C_PUT_DWORD (pTraceMsg->info.drvr_msg.dwDataPointerLow, dwLow);
         C_PUT_DWORD (pTraceMsg->info.drvr_msg.dwDataPointerHigh, dwHigh);
      }
   }
   
   // if we reach this point, there is a valid message buffer at m_pacMsgBuf and
   // there is a data block at m_paucDataBuf if a data block is attached to the
   // message
   return (true);
} // CTraceInterpreter::ReadTraceBlock





/**
 * Write a complete message and possible data block to the destination.
 */

void CTraceInterpreter::WriteTraceBlock (void)
{
   // check there is really a message in the buffer and the length specified is
   // valid
   if (! m_pacMsgBuf || m_nLenMsgBuf < sizeof (CAPITraceMsgHeader_t))
   {
      throw std::runtime_error
               ("CTraceInterpreter::WriteTraceBlock() called with invalid message buffer");
   }
   CAPITraceMsg_t *pTraceMsg = (CAPITraceMsg_t *) m_pacMsgBuf;
   size_t          nLen = C_GET_WORD (pTraceMsg->head.wLength);
   if (nLen > m_nLenMsgBuf)
   {
      throw std::runtime_error
               ("CTraceInterpreter::WriteTraceBlock() called with invalid message buffer content");
   }
   
   // check for the end-of-trace marker (only the trace message header and a
   // message type value of null)
   if (nLen == sizeof (pTraceMsg->head) &&
       C_GET_WORD (pTraceMsg->head.wMsgType) == 0)
   {
      struct timeval tv;
      
      tv.tv_sec = C_GET_DWORD (pTraceMsg->head.dwTimeSeconds);
      tv.tv_usec = C_GET_DWORD (pTraceMsg->head.dwTimeMicroSec);
      
      m_osOut << std::setfill ('-') << std::setw (60) << "" << newl;
      m_osOut << "Trace stopped at " << tv << newl;
      
      return;
   }
   
   // now there seems to be a valid message to be interpreted
   m_oTraceMsgPrint.Print (pTraceMsg, nLen);
   
} // CTraceInterpreter::WriteTraceBlock





/**
 * Ensure the buffer specified is large enough to hold a given number of
 * bytes.
 *
 * If the buffer specified through the first two arguments of this
 * function is already large enough to hold the number of bytes specified
 * through the third parameter, nothing is done and the function simply
 * returns at once.
 *
 * Else a new buffer is allocated. The number of bytes specified in the
 * fourth parameter is copied from the old buffer to the new one. Then the
 * old buffer is released and the new buffer is returned in place of the
 * old one.
 *
 * @param rpacBuf               I/O: The reference of the pointer to the
 *                                 original buffer space. The pointer value may
 *                                 be NULL when entering this function.
 * @param rnCurrLenBuf          I/O: The reference to the current length of the
 *                                 original buffer at rpacBuf.
 * @param nNewLen               I: The required new length of the buffer.
 * @param nPreserve             I: The number of bytes in the original buffer to
 *                                 preserve, i.e. to copy to the new one.
 *
 * @return Nothing.
 */

void CTraceInterpreter::Reserve
   (u_int8_t *&rpacBuf,
    size_t    &rnCurrLenBuf,
    size_t     nNewLen,
    size_t     nPreserve)
{
   // first check if the buffer is already large enough
   if (nNewLen <= rnCurrLenBuf)
   {
      return;
   }
   
   // allocate a new buffer of the requested length
   u_int8_t *pacTmp = new u_int8_t [nNewLen];
   
   // copy the bytes to preserve
   if (nPreserve > 0 && rpacBuf != NULL)
   {
      if (nPreserve > rnCurrLenBuf)
      {
         nPreserve = rnCurrLenBuf;
      }
      memcpy (pacTmp, rpacBuf, nPreserve);
   }
   
   // finally delete the old buffer and return the new one
   if (rpacBuf != NULL)
   {
      delete [] rpacBuf;
   }
   rpacBuf = pacTmp;
   rnCurrLenBuf = nNewLen;
   
} // CTraceInterpreter::Reserve





// === Implementation of public functions ================================





// === Implementation of private functions ===============================





/**
 * Stream output function for a struct timeval.
 */

static std::ostream & operator<<
   (std::ostream         &os,
    const struct timeval &tv)
{
   time_t    t;
   struct tm tm;
   char      szBuf [32];
   size_t    nLen;
   
   t = (time_t) tv.tv_sec;
   memset (&tm, 0, sizeof (tm));
   (void) localtime_r (&t, &tm);

   strftime (szBuf, sizeof (szBuf), "%Y-%m-%d %H:%M:%S", &tm);
   nLen = strlen (szBuf);
   snprintf (szBuf + nLen, sizeof (szBuf) - nLen, ".%03u",
             (unsigned) (tv.tv_usec / 1000));

   os << szBuf;

   return (os);
} // operator<<





/**
 * Stream output function for a CYesNo class.
 */
 
static std::ostream & operator<<
   (std::ostream &os,
    const CYesNo &yn)
{
   os << (yn.m_f ? "Yes" : "No");
   return (os);
} // operator<<
