//===-- StringExtractorGDBRemote.cpp --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/Utility/StringExtractorGDBRemote.h"

#include <cctype>
#include <cstring>
#include <optional>

StringExtractorGDBRemote::ResponseType
StringExtractorGDBRemote::GetResponseType() const {
  if (m_packet.empty())
    return eUnsupported;

  switch (m_packet[0]) {
  case 'E':
    if (isxdigit(m_packet[1]) && isxdigit(m_packet[2])) {
      if (m_packet.size() == 3)
        return eError;
      llvm::StringRef packet_ref(m_packet);
      if (packet_ref[3] == ';') {
        auto err_string = packet_ref.substr(4);
        for (auto e : err_string)
          if (!isxdigit(e))
            return eResponse;
        return eError;
      }
    }
    break;

  case 'O':
    if (m_packet.size() == 2 && m_packet[1] == 'K')
      return eOK;
    break;

  case '+':
    if (m_packet.size() == 1)
      return eAck;
    break;

  case '-':
    if (m_packet.size() == 1)
      return eNack;
    break;
  }
  return eResponse;
}

StringExtractorGDBRemote::ServerPacketType
StringExtractorGDBRemote::GetServerPacketType() const {
#define PACKET_MATCHES(s)                                                      \
  ((packet_size == (sizeof(s) - 1)) && (strcmp((packet_cstr), (s)) == 0))
#define PACKET_STARTS_WITH(s)                                                  \
  ((packet_size >= (sizeof(s) - 1)) &&                                         \
   ::strncmp(packet_cstr, s, (sizeof(s) - 1)) == 0)

  // Empty is not a supported packet...
  if (m_packet.empty())
    return eServerPacketType_invalid;

  const size_t packet_size = m_packet.size();
  const char *packet_cstr = m_packet.c_str();
  switch (m_packet[0]) {

  case '%':
    return eServerPacketType_notify;

  case '\x03':
    if (packet_size == 1)
      return eServerPacketType_interrupt;
    break;

  case '-':
    if (packet_size == 1)
      return eServerPacketType_nack;
    break;

  case '+':
    if (packet_size == 1)
      return eServerPacketType_ack;
    break;

  case 'A':
    return eServerPacketType_A;

  case 'Q':

    switch (packet_cstr[1]) {
    case 'E':
      if (PACKET_STARTS_WITH("QEnvironment:"))
        return eServerPacketType_QEnvironment;
      if (PACKET_STARTS_WITH("QEnvironmentHexEncoded:"))
        return eServerPacketType_QEnvironmentHexEncoded;
      if (PACKET_STARTS_WITH("QEnableErrorStrings"))
        return eServerPacketType_QEnableErrorStrings;
      break;

    case 'P':
      if (PACKET_STARTS_WITH("QPassSignals:"))
        return eServerPacketType_QPassSignals;
      break;

    case 'S':
      if (PACKET_MATCHES("QStartNoAckMode"))
        return eServerPacketType_QStartNoAckMode;
      if (PACKET_STARTS_WITH("QSaveRegisterState"))
        return eServerPacketType_QSaveRegisterState;
      if (PACKET_STARTS_WITH("QSetDisableASLR:"))
        return eServerPacketType_QSetDisableASLR;
      if (PACKET_STARTS_WITH("QSetDetachOnError:"))
        return eServerPacketType_QSetDetachOnError;
      if (PACKET_STARTS_WITH("QSetSTDIN:"))
        return eServerPacketType_QSetSTDIN;
      if (PACKET_STARTS_WITH("QSetSTDOUT:"))
        return eServerPacketType_QSetSTDOUT;
      if (PACKET_STARTS_WITH("QSetSTDERR:"))
        return eServerPacketType_QSetSTDERR;
      if (PACKET_STARTS_WITH("QSetWorkingDir:"))
        return eServerPacketType_QSetWorkingDir;
      if (PACKET_STARTS_WITH("QSetLogging:"))
        return eServerPacketType_QSetLogging;
      if (PACKET_STARTS_WITH("QSetIgnoredExceptions"))
        return eServerPacketType_QSetIgnoredExceptions;
      if (PACKET_STARTS_WITH("QSetMaxPacketSize:"))
        return eServerPacketType_QSetMaxPacketSize;
      if (PACKET_STARTS_WITH("QSetMaxPayloadSize:"))
        return eServerPacketType_QSetMaxPayloadSize;
      if (PACKET_STARTS_WITH("QSetEnableAsyncProfiling;"))
        return eServerPacketType_QSetEnableAsyncProfiling;
      if (PACKET_STARTS_WITH("QSyncThreadState:"))
        return eServerPacketType_QSyncThreadState;
      break;

    case 'L':
      if (PACKET_STARTS_WITH("QLaunchArch:"))
        return eServerPacketType_QLaunchArch;
      if (PACKET_MATCHES("QListThreadsInStopReply"))
        return eServerPacketType_QListThreadsInStopReply;
      break;

    case 'M':
      if (PACKET_STARTS_WITH("QMemTags"))
        return eServerPacketType_QMemTags;
      break;

    case 'N':
      if (PACKET_STARTS_WITH("QNonStop:"))
        return eServerPacketType_QNonStop;
      break;

    case 'R':
      if (PACKET_STARTS_WITH("QRestoreRegisterState:"))
        return eServerPacketType_QRestoreRegisterState;
      break;

    case 'T':
      if (PACKET_MATCHES("QThreadSuffixSupported"))
        return eServerPacketType_QThreadSuffixSupported;
      break;
    }
    break;

  case 'q':
    switch (packet_cstr[1]) {
    case 's':
      if (PACKET_MATCHES("qsProcessInfo"))
        return eServerPacketType_qsProcessInfo;
      if (PACKET_MATCHES("qsThreadInfo"))
        return eServerPacketType_qsThreadInfo;
      break;

    case 'f':
      if (PACKET_STARTS_WITH("qfProcessInfo"))
        return eServerPacketType_qfProcessInfo;
      if (PACKET_STARTS_WITH("qfThreadInfo"))
        return eServerPacketType_qfThreadInfo;
      break;

    case 'C':
      if (packet_size == 2)
        return eServerPacketType_qC;
      break;

    case 'E':
      if (PACKET_STARTS_WITH("qEcho:"))
        return eServerPacketType_qEcho;
      break;

    case 'F':
      if (PACKET_STARTS_WITH("qFileLoadAddress:"))
        return eServerPacketType_qFileLoadAddress;
      break;

    case 'G':
      if (PACKET_STARTS_WITH("qGroupName:"))
        return eServerPacketType_qGroupName;
      if (PACKET_MATCHES("qGetWorkingDir"))
        return eServerPacketType_qGetWorkingDir;
      if (PACKET_MATCHES("qGetPid"))
        return eServerPacketType_qGetPid;
      if (PACKET_STARTS_WITH("qGetProfileData;"))
        return eServerPacketType_qGetProfileData;
      if (PACKET_MATCHES("qGDBServerVersion"))
        return eServerPacketType_qGDBServerVersion;
      break;

    case 'H':
      if (PACKET_MATCHES("qHostInfo"))
        return eServerPacketType_qHostInfo;
      break;

    case 'K':
      if (PACKET_STARTS_WITH("qKillSpawnedProcess"))
        return eServerPacketType_qKillSpawnedProcess;
      break;

    case 'L':
      if (PACKET_STARTS_WITH("qLaunchGDBServer"))
        return eServerPacketType_qLaunchGDBServer;
      if (PACKET_MATCHES("qLaunchSuccess"))
        return eServerPacketType_qLaunchSuccess;
      break;

    case 'M':
      if (PACKET_STARTS_WITH("qMemoryRegionInfo:"))
        return eServerPacketType_qMemoryRegionInfo;
      if (PACKET_MATCHES("qMemoryRegionInfo"))
        return eServerPacketType_qMemoryRegionInfoSupported;
      if (PACKET_STARTS_WITH("qModuleInfo:"))
        return eServerPacketType_qModuleInfo;
      if (PACKET_STARTS_WITH("qMemTags:"))
        return eServerPacketType_qMemTags;
      break;

    case 'P':
      if (PACKET_STARTS_WITH("qProcessInfoPID:"))
        return eServerPacketType_qProcessInfoPID;
      if (PACKET_STARTS_WITH("qPlatform_shell:"))
        return eServerPacketType_qPlatform_shell;
      if (PACKET_STARTS_WITH("qPlatform_mkdir:"))
        return eServerPacketType_qPlatform_mkdir;
      if (PACKET_STARTS_WITH("qPlatform_chmod:"))
        return eServerPacketType_qPlatform_chmod;
      if (PACKET_MATCHES("qProcessInfo"))
        return eServerPacketType_qProcessInfo;
      if (PACKET_STARTS_WITH("qPathComplete:"))
        return eServerPacketType_qPathComplete;
      break;

    case 'Q':
      if (PACKET_MATCHES("qQueryGDBServer"))
        return eServerPacketType_qQueryGDBServer;
      break;

    case 'R':
      if (PACKET_STARTS_WITH("qRcmd,"))
        return eServerPacketType_qRcmd;
      if (PACKET_STARTS_WITH("qRegisterInfo"))
        return eServerPacketType_qRegisterInfo;
      break;

    case 'S':
      if (PACKET_STARTS_WITH("qSaveCore"))
        return eServerPacketType_qLLDBSaveCore;
      if (PACKET_STARTS_WITH("qSpeedTest:"))
        return eServerPacketType_qSpeedTest;
      if (PACKET_MATCHES("qShlibInfoAddr"))
        return eServerPacketType_qShlibInfoAddr;
      if (PACKET_MATCHES("qStepPacketSupported"))
        return eServerPacketType_qStepPacketSupported;
      if (PACKET_STARTS_WITH("qSupported"))
        return eServerPacketType_qSupported;
      if (PACKET_MATCHES("qSyncThreadStateSupported"))
        return eServerPacketType_qSyncThreadStateSupported;
      if (PACKET_MATCHES("qStructuredDataPlugins"))
        return eServerPacketType_qStructuredDataPlugins;
      break;

    case 'T':
      if (PACKET_STARTS_WITH("qThreadExtraInfo,"))
        return eServerPacketType_qThreadExtraInfo;
      if (PACKET_STARTS_WITH("qThreadStopInfo"))
        return eServerPacketType_qThreadStopInfo;
      break;

    case 'U':
      if (PACKET_STARTS_WITH("qUserName:"))
        return eServerPacketType_qUserName;
      break;

    case 'V':
      if (PACKET_MATCHES("qVAttachOrWaitSupported"))
        return eServerPacketType_qVAttachOrWaitSupported;
      break;

    case 'W':
      if (PACKET_STARTS_WITH("qWatchpointSupportInfo:"))
        return eServerPacketType_qWatchpointSupportInfo;
      if (PACKET_MATCHES("qWatchpointSupportInfo"))
        return eServerPacketType_qWatchpointSupportInfoSupported;
      break;

    case 'X':
      if (PACKET_STARTS_WITH("qXfer:"))
        return eServerPacketType_qXfer;
      break;
    }
    break;

  case 'j':
    if (PACKET_STARTS_WITH("jModulesInfo:"))
      return eServerPacketType_jModulesInfo;
    if (PACKET_MATCHES("jSignalsInfo"))
      return eServerPacketType_jSignalsInfo;
    if (PACKET_MATCHES("jThreadsInfo"))
      return eServerPacketType_jThreadsInfo;

    if (PACKET_MATCHES("jLLDBTraceSupported"))
      return eServerPacketType_jLLDBTraceSupported;
    if (PACKET_STARTS_WITH("jLLDBTraceStop:"))
      return eServerPacketType_jLLDBTraceStop;
    if (PACKET_STARTS_WITH("jLLDBTraceStart:"))
      return eServerPacketType_jLLDBTraceStart;
    if (PACKET_STARTS_WITH("jLLDBTraceGetState:"))
      return eServerPacketType_jLLDBTraceGetState;
    if (PACKET_STARTS_WITH("jLLDBTraceGetBinaryData:"))
      return eServerPacketType_jLLDBTraceGetBinaryData;
    break;

  case 'v':
    if (PACKET_STARTS_WITH("vFile:")) {
      if (PACKET_STARTS_WITH("vFile:open:"))
        return eServerPacketType_vFile_open;
      else if (PACKET_STARTS_WITH("vFile:close:"))
        return eServerPacketType_vFile_close;
      else if (PACKET_STARTS_WITH("vFile:pread"))
        return eServerPacketType_vFile_pread;
      else if (PACKET_STARTS_WITH("vFile:pwrite"))
        return eServerPacketType_vFile_pwrite;
      else if (PACKET_STARTS_WITH("vFile:size"))
        return eServerPacketType_vFile_size;
      else if (PACKET_STARTS_WITH("vFile:exists"))
        return eServerPacketType_vFile_exists;
      else if (PACKET_STARTS_WITH("vFile:fstat"))
        return eServerPacketType_vFile_fstat;
      else if (PACKET_STARTS_WITH("vFile:stat"))
        return eServerPacketType_vFile_stat;
      else if (PACKET_STARTS_WITH("vFile:mode"))
        return eServerPacketType_vFile_mode;
      else if (PACKET_STARTS_WITH("vFile:MD5"))
        return eServerPacketType_vFile_md5;
      else if (PACKET_STARTS_WITH("vFile:symlink"))
        return eServerPacketType_vFile_symlink;
      else if (PACKET_STARTS_WITH("vFile:unlink"))
        return eServerPacketType_vFile_unlink;

    } else {
      if (PACKET_STARTS_WITH("vAttach;"))
        return eServerPacketType_vAttach;
      if (PACKET_STARTS_WITH("vAttachWait;"))
        return eServerPacketType_vAttachWait;
      if (PACKET_STARTS_WITH("vAttachOrWait;"))
        return eServerPacketType_vAttachOrWait;
      if (PACKET_STARTS_WITH("vAttachName;"))
        return eServerPacketType_vAttachName;
      if (PACKET_STARTS_WITH("vCont;"))
        return eServerPacketType_vCont;
      if (PACKET_MATCHES("vCont?"))
        return eServerPacketType_vCont_actions;
      if (PACKET_STARTS_WITH("vKill;"))
        return eServerPacketType_vKill;
      if (PACKET_STARTS_WITH("vRun;"))
        return eServerPacketType_vRun;
      if (PACKET_MATCHES("vStopped"))
        return eServerPacketType_vStopped;
      if (PACKET_MATCHES("vCtrlC"))
        return eServerPacketType_vCtrlC;
      if (PACKET_MATCHES("vStdio"))
        return eServerPacketType_vStdio;
      break;

    }
    break;
  case '_':
    switch (packet_cstr[1]) {
    case 'M':
      return eServerPacketType__M;

    case 'm':
      return eServerPacketType__m;
    }
    break;

  case '?':
    if (packet_size == 1)
      return eServerPacketType_stop_reason;
    break;

  case 'c':
    return eServerPacketType_c;

  case 'C':
    return eServerPacketType_C;

  case 'D':
    return eServerPacketType_D;

  case 'g':
    return eServerPacketType_g;

  case 'G':
    return eServerPacketType_G;

  case 'H':
    return eServerPacketType_H;

  case 'I':
    return eServerPacketType_I;

  case 'k':
    if (packet_size == 1)
      return eServerPacketType_k;
    break;

  case 'm':
    return eServerPacketType_m;

  case 'M':
    return eServerPacketType_M;

  case 'p':
    return eServerPacketType_p;

  case 'P':
    return eServerPacketType_P;

  case 's':
    if (packet_size == 1)
      return eServerPacketType_s;
    break;

  case 'S':
    return eServerPacketType_S;

  case 'x':
    return eServerPacketType_x;

  case 'X':
    return eServerPacketType_X;

  case 'T':
    return eServerPacketType_T;

  case 'z':
    if (packet_cstr[1] >= '0' && packet_cstr[1] <= '4')
      return eServerPacketType_z;
    break;

  case 'Z':
    if (packet_cstr[1] >= '0' && packet_cstr[1] <= '4')
      return eServerPacketType_Z;
    break;
  }
  return eServerPacketType_unimplemented;
}

bool StringExtractorGDBRemote::IsOKResponse() const {
  return GetResponseType() == eOK;
}

bool StringExtractorGDBRemote::IsUnsupportedResponse() const {
  return GetResponseType() == eUnsupported;
}

bool StringExtractorGDBRemote::IsNormalResponse() const {
  return GetResponseType() == eResponse;
}

bool StringExtractorGDBRemote::IsErrorResponse() const {
  return GetResponseType() == eError && isxdigit(m_packet[1]) &&
         isxdigit(m_packet[2]);
}

uint8_t StringExtractorGDBRemote::GetError() {
  if (GetResponseType() == eError) {
    SetFilePos(1);
    return GetHexU8(255);
  }
  return 0;
}

lldb_private::Status StringExtractorGDBRemote::GetStatus() {
  lldb_private::Status error;
  if (GetResponseType() == eError) {
    SetFilePos(1);
    uint8_t errc = GetHexU8(255);
    error = lldb_private::Status::FromErrorStringWithFormat("Error %u", errc);
    std::string error_messg;
    if (GetChar() == ';') {
      GetHexByteString(error_messg);
      error = lldb_private::Status(error_messg);
    }
  }
  return error;
}

size_t StringExtractorGDBRemote::GetEscapedBinaryData(std::string &str) {
  // Just get the data bytes in the string as
  // GDBRemoteCommunication::CheckForPacket() already removes any 0x7d escaped
  // characters. If any 0x7d characters are left in the packet, then they are
  // supposed to be there...
  str.clear();
  const size_t bytes_left = GetBytesLeft();
  if (bytes_left > 0) {
    str.assign(m_packet, m_index, bytes_left);
    m_index += bytes_left;
  }
  return str.size();
}

static bool
OKErrorNotSupportedResponseValidator(void *,
                                     const StringExtractorGDBRemote &response) {
  switch (response.GetResponseType()) {
  case StringExtractorGDBRemote::eOK:
  case StringExtractorGDBRemote::eError:
  case StringExtractorGDBRemote::eUnsupported:
    return true;

  case StringExtractorGDBRemote::eAck:
  case StringExtractorGDBRemote::eNack:
  case StringExtractorGDBRemote::eResponse:
    break;
  }
  return false;
}

static bool JSONResponseValidator(void *,
                                  const StringExtractorGDBRemote &response) {
  switch (response.GetResponseType()) {
  case StringExtractorGDBRemote::eUnsupported:
  case StringExtractorGDBRemote::eError:
    return true; // Accept unsupported or EXX as valid responses

  case StringExtractorGDBRemote::eOK:
  case StringExtractorGDBRemote::eAck:
  case StringExtractorGDBRemote::eNack:
    break;

  case StringExtractorGDBRemote::eResponse:
    // JSON that is returned in from JSON query packets is currently always
    // either a dictionary which starts with a '{', or an array which starts
    // with a '['. This is a quick validator to just make sure the response
    // could be valid JSON without having to validate all of the
    // JSON content.
    switch (response.GetStringRef()[0]) {
    case '{':
      return true;
    case '[':
      return true;
    default:
      break;
    }
    break;
  }
  return false;
}

static bool
ASCIIHexBytesResponseValidator(void *,
                               const StringExtractorGDBRemote &response) {
  switch (response.GetResponseType()) {
  case StringExtractorGDBRemote::eUnsupported:
  case StringExtractorGDBRemote::eError:
    return true; // Accept unsupported or EXX as valid responses

  case StringExtractorGDBRemote::eOK:
  case StringExtractorGDBRemote::eAck:
  case StringExtractorGDBRemote::eNack:
    break;

  case StringExtractorGDBRemote::eResponse: {
    uint32_t valid_count = 0;
    for (const char ch : response.GetStringRef()) {
      if (!isxdigit(ch)) {
        return false;
      }
      if (++valid_count >= 16)
        break; // Don't validate all the characters in case the packet is very
               // large
    }
    return true;
  } break;
  }
  return false;
}

void StringExtractorGDBRemote::CopyResponseValidator(
    const StringExtractorGDBRemote &rhs) {
  m_validator = rhs.m_validator;
  m_validator_baton = rhs.m_validator_baton;
}

void StringExtractorGDBRemote::SetResponseValidator(
    ResponseValidatorCallback callback, void *baton) {
  m_validator = callback;
  m_validator_baton = baton;
}

void StringExtractorGDBRemote::SetResponseValidatorToOKErrorNotSupported() {
  m_validator = OKErrorNotSupportedResponseValidator;
  m_validator_baton = nullptr;
}

void StringExtractorGDBRemote::SetResponseValidatorToASCIIHexBytes() {
  m_validator = ASCIIHexBytesResponseValidator;
  m_validator_baton = nullptr;
}

void StringExtractorGDBRemote::SetResponseValidatorToJSON() {
  m_validator = JSONResponseValidator;
  m_validator_baton = nullptr;
}

bool StringExtractorGDBRemote::ValidateResponse() const {
  // If we have a validator callback, try to validate the callback
  if (m_validator)
    return m_validator(m_validator_baton, *this);
  else
    return true; // No validator, so response is valid
}

std::optional<std::pair<lldb::pid_t, lldb::tid_t>>
StringExtractorGDBRemote::GetPidTid(lldb::pid_t default_pid) {
  llvm::StringRef view = llvm::StringRef(m_packet).substr(m_index);
  size_t initial_length = view.size();
  lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
  lldb::tid_t tid;

  if (view.consume_front("p")) {
    // process identifier
    if (view.consume_front("-1")) {
      // -1 is a special case
      pid = AllProcesses;
    } else if (view.consumeInteger(16, pid) || pid == 0) {
      // not a valid hex integer OR unsupported pid 0
      m_index = UINT64_MAX;
      return std::nullopt;
    }

    // "." must follow if we expect TID too; otherwise, we assume -1
    if (!view.consume_front(".")) {
      // update m_index
      m_index += initial_length - view.size();

      return {{pid, AllThreads}};
    }
  }

  // thread identifier
  if (view.consume_front("-1")) {
    // -1 is a special case
    tid = AllThreads;
  } else if (view.consumeInteger(16, tid) || tid == 0 || pid == AllProcesses) {
    // not a valid hex integer OR tid 0 OR pid -1 + a specific tid
    m_index = UINT64_MAX;
    return std::nullopt;
  }

  // update m_index
  m_index += initial_length - view.size();

  return {{pid != LLDB_INVALID_PROCESS_ID ? pid : default_pid, tid}};
}
