mirror of
https://github.com/HyperDbg/HyperDbg.git
synced 2026-07-10 01:29:59 +00:00
380 lines
10 KiB
C++
380 lines
10 KiB
C++
/**
|
|
* @file test-parser.cpp
|
|
* @author Sina Karvandi (sina@hyperdbg.org)
|
|
* @brief Perform test on command parser
|
|
* @details
|
|
* @version 0.11
|
|
* @date 2024-08-11
|
|
*
|
|
* @copyright This project is released under the GNU Public License v3.
|
|
*
|
|
*/
|
|
#include "pch.h"
|
|
|
|
typedef CHAR ** CHAR_PTR_PTR; // Define CHAR_PTR_PTR as CHAR**
|
|
|
|
/**
|
|
* @brief Create an array of strings from a vector of strings
|
|
* @param TestCases The vector of strings to copy
|
|
*
|
|
* @return A pointer to the array of strings
|
|
*/
|
|
CHAR_PTR_PTR
|
|
CreateTestCaseArray(const std::vector<std::string> & TestCases)
|
|
{
|
|
//
|
|
// Allocate memory for the array of pointers (size: number of test cases)
|
|
//
|
|
CHAR_PTR_PTR TestCaseArray = (CHAR_PTR_PTR)malloc(TestCases.size() * sizeof(UINT64));
|
|
|
|
//
|
|
// Allocate memory for each string and copy the content
|
|
//
|
|
for (SIZE_T i = 0; i < TestCases.size(); ++i)
|
|
{
|
|
TestCaseArray[i] = (CHAR *)malloc(TestCases[i].length() + 1); // +1 for the null terminator
|
|
|
|
if (TestCaseArray[i] == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
std::strcpy(TestCaseArray[i], TestCases[i].c_str());
|
|
}
|
|
|
|
return TestCaseArray;
|
|
}
|
|
|
|
/**
|
|
* @brief Free the memory allocated for the test case array
|
|
* @param TestCaseArray The array of pointers to free
|
|
* @param Size The size of the array
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
FreeTestCaseArray(CHAR_PTR_PTR TestCaseArray, SIZE_T Size)
|
|
{
|
|
//
|
|
// Free each string
|
|
//
|
|
for (SIZE_T i = 0; i < Size; ++i)
|
|
{
|
|
free(TestCaseArray[i]);
|
|
}
|
|
|
|
//
|
|
// Free the array of pointers
|
|
//
|
|
free(TestCaseArray);
|
|
}
|
|
|
|
/**
|
|
* @brief Parse the test cases from the file
|
|
* @param Filename The name of the file to parse
|
|
*
|
|
* @return A vector of pairs, where each pair contains a command and a vector of tokens
|
|
*/
|
|
std::vector<std::pair<std::string, std::vector<std::string>>>
|
|
ParseTestCases(const std::string & Filename)
|
|
{
|
|
std::ifstream file(Filename);
|
|
std::string Line;
|
|
std::string Command;
|
|
std::string CurrentToken;
|
|
std::vector<std::string> Tokens;
|
|
std::vector<std::pair<std::string, std::vector<std::string>>> TestCases;
|
|
BOOLEAN IsCommand = FALSE;
|
|
BOOLEAN AddNewline = FALSE;
|
|
|
|
const std::string TokenDelimiter = "----------------------------------";
|
|
const std::string CommandDelimiter = "_____________________________________________________________";
|
|
|
|
while (std::getline(file, Line))
|
|
{
|
|
if (Line == CommandDelimiter)
|
|
{
|
|
//
|
|
// No new line is needed after this token
|
|
//
|
|
AddNewline = FALSE;
|
|
|
|
//
|
|
// Save the previous command and its tokens if any
|
|
//
|
|
if (!Command.empty())
|
|
{
|
|
if (!CurrentToken.empty())
|
|
{
|
|
Tokens.push_back(CurrentToken);
|
|
CurrentToken.clear();
|
|
}
|
|
TestCases.push_back({Command, Tokens});
|
|
Command.clear();
|
|
Tokens.clear();
|
|
}
|
|
|
|
//
|
|
// is command is true since a command is started
|
|
//
|
|
IsCommand = TRUE;
|
|
}
|
|
else if (Line == TokenDelimiter)
|
|
{
|
|
//
|
|
// No new line is needed after this token
|
|
//
|
|
AddNewline = FALSE;
|
|
|
|
//
|
|
// not in command anymore
|
|
//
|
|
IsCommand = FALSE;
|
|
|
|
//
|
|
// If we're in the middle of collecting a token, save it
|
|
//
|
|
if (!CurrentToken.empty())
|
|
{
|
|
Tokens.push_back(CurrentToken);
|
|
CurrentToken.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Accumulate lines for the command or token
|
|
//
|
|
if (IsCommand)
|
|
{
|
|
if (AddNewline)
|
|
Command += "\n";
|
|
Command += Line;
|
|
}
|
|
else
|
|
{
|
|
if (AddNewline)
|
|
CurrentToken += "\n";
|
|
CurrentToken += Line;
|
|
}
|
|
|
|
AddNewline = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Store the last command and tokens if any
|
|
//
|
|
if (!Command.empty())
|
|
{
|
|
if (!CurrentToken.empty())
|
|
{
|
|
Tokens.push_back(CurrentToken);
|
|
}
|
|
TestCases.push_back({Command, Tokens});
|
|
}
|
|
|
|
return TestCases;
|
|
}
|
|
|
|
/**
|
|
* @brief Count the number of occurrences of the substring "\\n" up to a specified position
|
|
* @param Str The string to search
|
|
* @param Limit The position to search up to
|
|
*
|
|
* @return INT32 The number of occurrences of the substring "\\n"
|
|
*/
|
|
INT32
|
|
CountBackslashNUpToPosition(const std::string & Str, std::size_t Limit)
|
|
{
|
|
INT32 Count = 0;
|
|
std::string::size_type Pos = 0;
|
|
std::string Target = "\\n";
|
|
|
|
//
|
|
// Limit the string to search within the specified range
|
|
//
|
|
while ((Pos = Str.find(Target, Pos)) != std::string::npos && Pos < Limit)
|
|
{
|
|
++Count;
|
|
Pos += Target.length(); // Move past the current occurrence
|
|
}
|
|
|
|
return Count;
|
|
}
|
|
|
|
/**
|
|
* @brief Show parsed command and tokens
|
|
* @param TestCase A pair containing a command and a vector of tokens
|
|
* @param FailedTokenNum The number of the failed token
|
|
* @param FailedTokenPosition The position of the failed token
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
ShowParsedCommandAndTokens(const std::pair<std::string,
|
|
std::vector<std::string>> & TestCase,
|
|
UINT32 FailedTokenNum,
|
|
UINT32 FailedTokenPosition)
|
|
{
|
|
UINT32 TokenNum = 0;
|
|
|
|
//
|
|
// Output the parsed test case
|
|
//
|
|
|
|
string ShowingCommand = TestCase.first;
|
|
|
|
std::string::size_type Pos = 0;
|
|
while ((Pos = ShowingCommand.find("\n", Pos)) != std::string::npos)
|
|
{
|
|
ShowingCommand.replace(Pos, 1, "\\n");
|
|
Pos += 2; // Move past the newly added characters
|
|
}
|
|
|
|
std::cout << "Command: \"" << ShowingCommand << "\"" << std::endl;
|
|
std::cout << "____________________________________\n";
|
|
|
|
std::cout << "Expected Tokens: " << std::endl;
|
|
|
|
for (const auto & Token : TestCase.second)
|
|
{
|
|
string ShowingToken = Token;
|
|
|
|
Pos = 0;
|
|
while ((Pos = ShowingToken.find("\n", Pos)) != std::string::npos)
|
|
{
|
|
ShowingToken.replace(Pos, 1, "\\n");
|
|
Pos += 2; // Move past the newly added characters
|
|
}
|
|
|
|
if (TokenNum == FailedTokenNum)
|
|
{
|
|
std::cout << " x ";
|
|
}
|
|
else
|
|
{
|
|
std::cout << " - ";
|
|
}
|
|
|
|
std::cout << "\"" << ShowingToken << "\"" << std::endl;
|
|
|
|
if (TokenNum == FailedTokenNum)
|
|
{
|
|
std::cout << " ";
|
|
INT32 CountOfSpaces = CountBackslashNUpToPosition(ShowingToken, FailedTokenPosition);
|
|
|
|
CountOfSpaces += FailedTokenPosition;
|
|
|
|
for (INT32 i = 0; i < CountOfSpaces; i++)
|
|
{
|
|
std::cout << " ";
|
|
}
|
|
std::cout << "^" << std::endl;
|
|
}
|
|
|
|
TokenNum++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Test command parser
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
TestCommandParser()
|
|
{
|
|
BOOLEAN OverallResult = TRUE;
|
|
INT32 TestNum = 0;
|
|
CHAR FilePath[MAX_PATH] = {0};
|
|
UINT32 FailedTokenNum = 0;
|
|
UINT32 FailedTokenPosition = 0;
|
|
|
|
//
|
|
// Parse the test cases from the file
|
|
// Setup the path for the filename
|
|
//
|
|
if (!hyperdbg_u_setup_path_for_filename(COMMAND_PARSER_TEST_CASES_FILE, FilePath, MAX_PATH, TRUE))
|
|
{
|
|
//
|
|
// Error could not find the test case files
|
|
//
|
|
cout << "[-] Could not find the test case files" << endl;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Parse the test cases from the file
|
|
//
|
|
auto TestCases = ParseTestCases(FilePath);
|
|
|
|
//
|
|
// Perform testing test cases with parsed file
|
|
//
|
|
cout << "Perform testing test cases with parsed file:" << endl;
|
|
|
|
//
|
|
// Output the parsed test cases
|
|
//
|
|
for (const auto & TestCase : TestCases)
|
|
{
|
|
TestNum++;
|
|
|
|
//
|
|
// Create CHAR**
|
|
//
|
|
CHAR_PTR_PTR TestCaseArray = CreateTestCaseArray(TestCase.second);
|
|
|
|
//
|
|
// Check token with actual parser
|
|
//
|
|
if (hyperdbg_u_test_command_parser((CHAR *)TestCase.first.c_str(),
|
|
(UINT32)TestCase.second.size(),
|
|
TestCaseArray,
|
|
&FailedTokenNum,
|
|
&FailedTokenPosition))
|
|
{
|
|
cout << "[+] Test number " << TestNum << " Passed " << endl;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Set overall result to FALSE since one of the test cases failed
|
|
//
|
|
OverallResult = FALSE;
|
|
|
|
//
|
|
// Show parsed command and tokens
|
|
//
|
|
cout << "\n============================================================" << endl;
|
|
cout << "\n********************* " << endl;
|
|
cout << "*** Error details *** " << endl;
|
|
cout << "********************* " << endl;
|
|
cout << "\nParsed tokens from HyperDbg main command parser:\n"
|
|
<< endl;
|
|
|
|
//
|
|
// Show tokens
|
|
//
|
|
hyperdbg_u_test_command_parser_show_tokens((CHAR *)TestCase.first.c_str());
|
|
|
|
cout << "\n============================================================" << endl;
|
|
|
|
cout << "\nThe parsed command and tokens (From file):" << endl;
|
|
ShowParsedCommandAndTokens(TestCase, FailedTokenNum, FailedTokenPosition);
|
|
|
|
cout << "\n[-] Test number " << TestNum << " Failed " << endl;
|
|
cout << "============================================================\n"
|
|
<< endl;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Clean up memory
|
|
//
|
|
FreeTestCaseArray(TestCaseArray, TestCase.second.size());
|
|
}
|
|
|
|
return OverallResult;
|
|
}
|