//
//
//      This file contains win32 specific implementations of win32_debugger_module class
//      server-side functionality only
//
//

#include <pro.h>
#include "win32_rpc.h"
#include "win32_debmod.h"
#include "rpc_hlp.h"

#ifdef ENABLE_REMOTEPDB

#include "tilfuncs.hpp"

//---------------------------------------------------------- main thread ---
void win32_debmod_t::handle_pdb_thread_request(void *_fetcher)
{
  pdb_fetcher_t *fetcher = (pdb_fetcher_t *) _fetcher;
  pdb_fetcher_t::opener_thread_t &thread = fetcher->opener_thread;
  if ( thread.read_request.kind == pdb_fetcher_t::opener_thread_t::read_request_t::read_input_file )
  {
    // read input file
    bytevec_t cmd;
    append_dq(cmd,  thread.read_request.off_ea);
    append_dd(cmd,  thread.read_request.size);
    append_str(cmd, fetcher->pdbfile.c_str());
    void *outbuf = NULL;
    ssize_t outsize = 0;
    // send request to IDA
    int rc = send_ioctl(WIN32_IOCTL_READFILE, &cmd[0], cmd.size(), &outbuf, &outsize);
    if ( rc == 1 && outbuf != NULL )
    {
      // OK
      size_t copylen = qmin(thread.read_request.size, outsize);
      memcpy(thread.read_request.buffer, outbuf, copylen);
      thread.read_request.size   = copylen;
      thread.read_request.result = true;
    }
    else
    {
      thread.read_request.result = false;
    }
    if ( outbuf != NULL )
      qfree(outbuf);
  }
  else if ( thread.read_request.kind == pdb_fetcher_t::opener_thread_t::read_request_t::read_memory )
  {
    // read memory
    ssize_t rc = _read_memory(ea_t(thread.read_request.off_ea), thread.read_request.buffer, thread.read_request.size);
    if ( rc >= 0 )
      thread.read_request.size = rc;
    thread.read_request.result = rc >= 0;
  }
  else
  {
    // unknown request
    thread.read_request.result = false;
  }

  fetcher->opener_thread.read_request.req_complete();
}

// ---------------------------------------------------------------------------
struct pdb_remote_session_t
{
  ~pdb_remote_session_t()
  {
    if ( fetcher.opener_thread.is_running() )
      qsleep(1000);
    if ( fetcher.opener_thread.is_running() )
      fetcher.opener_thread.kill();
  }

  pdb_fetcher_t fetcher;
};

// ---------------------------------------------------------------------------
void close_pdb_remote_session(pdb_remote_session_t *session)
{
  delete session;
}

#else

void close_pdb_remote_session(pdb_remote_session_t *)
{
}

#endif

// ---------------------------------------------------------------------------
static int pdb_ioctl_error(void **poutbuf, ssize_t *poutsize, const char *format, ...)
{
  msg("ERROR: ");
  char buf[MAXSTR];
  va_list va;
  va_start(va, format);
  qvsnprintf(buf, sizeof(buf), format, va);
  va_end(va);
  msg(buf);
  *poutsize = strlen(buf) + 1;
  *poutbuf  = qstrdup(buf);
  return pdb_error;
}

//---------------------------------------------------------- main thread ---
int idaapi win32_debmod_t::handle_ioctl(
      int fn,
      const void *buf,
      size_t size,
      void **poutbuf,
      ssize_t *poutsize)
{
  qnotused(size);
  switch ( fn )
  {
    case WIN32_IOCTL_RDMSR:
      QASSERT(30119, size == sizeof(uval_t));
      {
        uint64 value;
        uval_t reg = *(uval_t *)buf;
        int code = rdmsr(reg, &value);
        if ( SUCCEEDED(code) )
        {
          *poutbuf = qalloc(sizeof(value));
          if ( *poutbuf != NULL )
          {
            memcpy(*poutbuf, &value, sizeof(value));
            *poutsize = sizeof(value);
          }
        }
        return code;
      }

    case WIN32_IOCTL_WRMSR:
      QASSERT(30120, size == sizeof(win32_wrmsr_t));
      {
        win32_wrmsr_t &msr = *(win32_wrmsr_t *)buf;
        return wrmsr(msr.reg, msr.value);
      }

#ifdef ENABLE_REMOTEPDB
    case WIN32_IOCTL_PDB_OPEN:
      {
        const uchar *ptr = (const uchar *)buf;
        const uchar *end = ptr + size;

        this->pdb_remote_session = new pdb_remote_session_t();

        // msg("New session\n");
        compiler_info_t compiler_info;
        extract_memory(&ptr, end, &compiler_info, sizeof(compiler_info));
        ea_t        base_ea = extract_ea64(&ptr, end);
        const char *pdbfile = extract_str(&ptr, end);
        const char *dpath   = extract_str(&ptr, end);
        const char *spath   = extract_str(&ptr, end);
        this->pdb_remote_session->fetcher.open(compiler_info, base_ea, pdbfile, dpath, spath);
        *poutsize = 0;
        *poutbuf  = NULL;
      }
      return pdb_ok;

    case WIN32_IOCTL_PDB_OPERATION_COMPLETE:
      // This is used, in a polling fashion, by the the client,
      // to check on completeness of the fetch. At the same time,
      // this is our wake-up call for looking whether the
      // fetch thread requires more information.
      {
        // msg("Checking for completeness of op\n");
        if ( this->pdb_remote_session == NULL )
          return pdb_ioctl_error(poutbuf, poutsize, "Session not opened");

        // If we've got a read request, handle it now
        if ( this->pdb_remote_session->fetcher.opener_thread.read_request.pending() )
          handle_pdb_thread_request(&this->pdb_remote_session->fetcher);

        if ( !this->pdb_remote_session->fetcher.opener_thread.is_done() )
        {
          return pdb_operation_incomplete;
        }
        else
        {
          if ( this->pdb_remote_session->fetcher.opener_thread.result.ok )
          {
            bytevec_t storage;
            pdb_sym_t sym = 0;
            local_pdb_access_t *acc = this->pdb_remote_session->fetcher.session_ref->session->pdb_access;
            acc->dia_global->get_symIndexId((DWORD *) &sym);
            append_dq(storage, sym);
            append_dd(storage, acc->get_machine_type());
            append_dd(storage, acc->get_dia_version());
            *poutsize = storage.size();
            *poutbuf  = storage.extract();
            return pdb_operation_complete;
          }
          else
          {
            msg("Failed opening file; destroying session\n");
            delete this->pdb_remote_session;
            this->pdb_remote_session = NULL;
            return pdb_ioctl_error(poutbuf, poutsize, "Cannot open PDB");
          }
        }
      }

    case WIN32_IOCTL_PDB_FETCH_SYMBOL:
    case WIN32_IOCTL_PDB_FETCH_CHILDREN:
      {
        const uchar *ptr = (const uchar *)buf;
        const uchar *end = ptr + size;

        pdb_sym_t sym = (pdb_sym_t) extract_uint64(&ptr, end);

        // msg("Fetch%s 0x%x\n",
        //     (fn == WIN32_IOCTL_PDB_FETCH_CHILDREN ? " children for" : ""),
        //     (uint32) sym);

        if ( this->pdb_remote_session == NULL )
          return pdb_ioctl_error(poutbuf, poutsize, "Session not opened");

        bool ok;
        if ( fn == WIN32_IOCTL_PDB_FETCH_SYMBOL )
        {
          // Symbol
          ok = this->pdb_remote_session->fetcher.fetch_symbol(sym);
        }
        else
        {
          // Children
          enum SymTagEnum children_type = (enum SymTagEnum) extract_long(&ptr, end);
          ok = this->pdb_remote_session->fetcher.fetch_children(sym, children_type);
        }

        if ( ok )
        {
          size_t sz  = this->pdb_remote_session->fetcher.storage.size();
          uint8 *raw = (uint8 *) qalloc(sz);
          if ( raw == NULL )
            return pdb_error;
          memcpy(raw, this->pdb_remote_session->fetcher.storage.begin(), sz);
          *poutbuf   = raw;
          *poutsize  = sz;
        }

        return ok ? pdb_ok : pdb_error;
      }

    case WIN32_IOCTL_PDB_CLOSE:
      {
        // msg("Closing session\n");
        if ( this->pdb_remote_session == NULL )
          return pdb_ioctl_error(poutbuf, poutsize, "Session not opened");

        delete this->pdb_remote_session;
        this->pdb_remote_session = NULL;

        return pdb_ok;
      }


#endif // REMOTEPDB

    default:
      break;
  }
  return 0;
}
