Я работаю над приложением, которое работает как «человек посередине» для анализа протокола (ISO 8583), отправленного через TCP/IP.
Основная идея состоит в том, чтобы получить необработанные двоичные данные и преобразовать их в строку для разбора и декодирования протокола.
Для этого я использую компонент TIdMappedPortTCP
.
Тестирую с Hercules.
Я работаю с:
Windows 11 Домашняя
Embarcadero® C++Builder 10.4 Версия 27.0.40680.4203
Delphi и C++ Builder 10.4, обновление 2
Инди 10.6.2.0
Больше контекста можно найти в этих вопросах:
Где я могу найти полностью рабочий пример TCP-клиента и сервера для Indy в C++Builder?
Разбор байтов как BCD с помощью Indy C++ Builder
Проблема в том, что мне нужно отправить сообщение дважды, чтобы вызвать событие OnExecute
. Я думаю, что это может быть связано с длиной, но я не нашел проблему. В остальном программа делает то, что от нее ожидается.
Если я использую эти данные в Hercules:
00 04 60 02
эквивалентно:
"\x00\x04\x60\x02"
Моя программа обрабатывает все правильно:
Вот код:
void __fastcall TForm1::MITMProxyExecute(TIdContext *AContext)
{
static int index;
TIdBytes ucBuffer;
UnicodeString usTemp1;
UnicodeString usTemp2;
int calculated_length;
// getting the length in Hexa
calculated_length = ReadMessageLength(AContext);
// reads data
AContext->Connection->IOHandler->ReadBytes(ucBuffer, calculated_length);
// displays string with calculated length and size of the data
usTemp2 = UnicodeString("calculated length = ");
usTemp2 += IntToStr(calculated_length);
usTemp2 += " ucBuffer.Length = ";
usTemp2 += IntToStr(ucBuffer.Length);
Display->Lines->Add(usTemp2);
// converts the binary data into a a Hex String for visualization
usTemp1 = BytesToHexString(ucBuffer);
// adds an index to distinguish from previous entries.
usTemp2 = IntToStr(index);
usTemp2 += UnicodeString(": ");
usTemp2 += usTemp1;
Display->Lines->Add(usTemp2);
index++;
}
Вот код вызываемых там функций. Кстати, есть ли лучший способ преобразовать байты в шестнадцатеричную строку?
// Convert an array of bytes to a hexadecimal string
UnicodeString BytesToHexString(const TBytes& bytes)
{
// Create an empty UnicodeString to store the hexadecimal representation of the bytes
UnicodeString hexString;
// Iterate through each byte in the array
for (int i = 0; i < bytes.Length; i++)
{
// Convert the byte to a hexadecimal string and append it to the result string
hexString += IntToHex(bytes[i], 2);
}
// Return the hexadecimal string
return hexString;
}
// Read the first two bytes of an incoming message and interpret them as the length of the message
int ReadMessageLength(TIdContext *AContext)
{
int calculated_length;
// Use the 'ReadSmallInt' method to read the length of the message from the first two bytes
calculated_length = AContext->Connection->IOHandler->ReadSmallInt();
// converting from hex binary to hex string
UnicodeString bcdLength = UnicodeString().sprintf(L"%04x", calculated_length);
// converting from hex string to int
calculated_length = bcdLength.ToInt();
// decrease length
calculated_length -= 2;
return calculated_length;
}
ОБНОВЛЯТЬ Я создал класс для обновления элемента управления TEditRich. Но проблема сохраняется, мне нужно дважды отправить сообщение для обработки, и приложение зависает при попытке закрыть его. Это мой класс:
class TAddTextToDisplay : public TIdSync {
private:
UnicodeString textToAdd;
public:
__fastcall TAddTextToDisplay(UnicodeString str) {
// Store the input parameters in member variables.
textToAdd = str;
}
virtual void __fastcall DoSynchronize() {
if (textToAdd != NULL) {
// Use the input parameters here...
Form1->Display->Lines->Add(textToAdd);
}
}
void __fastcall setTextToAdd(UnicodeString str) {
textToAdd = str;
}
};
А вот так выглядит мое новое событие OnExecute:
void __fastcall TForm1::MITMProxyExecute(TIdContext *AContext) {
static int index;
TIdBytes ucBuffer;
UnicodeString usTemp1;
UnicodeString usTemp2;
int calculated_length;
int bytes_remaining;
// getting the length in Hexa
calculated_length = ReadMessageLength(AContext);
if (!AContext->Connection->IOHandler->InputBufferIsEmpty()) {
// reads data
AContext->Connection->IOHandler->ReadBytes(ucBuffer, calculated_length);
// displays string with calculated length and size of the data
usTemp2 = UnicodeString("calculated length = ");
usTemp2 += IntToStr(calculated_length);
usTemp2 += " ucBuffer.Length = ";
usTemp2 += IntToStr(ucBuffer.Length);
TAddTextToDisplay *AddTextToDisplay = new TAddTextToDisplay(usTemp2);
AddTextToDisplay->Synchronize();
// converts the binary data into a a Hex String for visualization
usTemp1 = BytesToHexString(ucBuffer);
// adds an index to distinguish from previous entries.
usTemp2 = IntToStr(index);
usTemp2 += UnicodeString(": ");
usTemp2 += usTemp1;
AddTextToDisplay->setTextToAdd(usTemp2);
AddTextToDisplay->Synchronize();
delete AddTextToDisplay;
index++;
}
}
Спасибо, я этого не знал. Кажется, это проблема, поскольку приложение не может выйти, оно зависает, когда я пытаюсь его закрыть.
@RemyLebeau Я добавил новый класс и изменил событие OnExecute (см. обновление внизу), но проблема не устранена. Любая помощь будет высоко оценена.
Вы действительно не должны читать прямо из IOHandler
. Ваше общение не синхронизировано. TIdMappedPortTCP
выполняет внутреннее чтение с клиента перед запуском события OnExecute
и чтение с целевого сервера перед запуском события OnOutboundData
. В обоих случаях полученные байты становятся доступными в свойстве TIdMappedPortContext::NetData
, которое вы вообще не обрабатываете.
Вам нужно выполнить весь синтаксический анализ, используя только NetData
, перебирая его байты в поисках полных сообщений и сохраняя неполные сообщения для будущих событий.
Вместо этого попробуйте что-то вроде этого:
#include <IdGlobal.hpp>
#include <IdBuffer.hpp>
bool ReadMessageData(TIdBuffer *Buffer, int &Offset, TIdBytes &Data)
{
// has enough bytes?
if ((Offset + 2) > Buffer->Size)
return false;
// read the length of the message from the first two bytes
UInt16 binLength = Buffer->ExtractToUInt16(Offset);
// converting from hex binary to hex string
String bcdLength = String().sprintf(_D("%04hx"), binLength);
// converting from hex string to int
int calculated_length = bcdLength.ToInt() - 2;
// has enough bytes?
if ((Offset + 2 + calculated_length) > Buffer->Size)
return false;
// reads data
Data.Length = calculated_length;
Buffer->ExtractToBytes(Data, calculated_length, false, Offset + 2);
Offset += (2 + calculated_length);
return true;
}
void __fastcall TForm1::MITMProxyConnect(TIdContext *AContext)
{
AContext->Data = new TIdBuffer;
}
void __fastcall TForm1::MITMProxyDisconnect(TIdContext *AContext)
{
delete static_cast<TIdBuffer*>(AContext->Data);
AContext->Data = NULL;
}
void __fastcall TForm1::MITMProxyExecute(TIdContext *AContext)
{
static int index = 0;
TIdBuffer *Buffer = static_cast<TIdBuffer*>(AContext->Data);
Buffer->Write(static_cast<TIdMappedPortContext*>(AContext)->NetData);
Buffer->CompactHead();
TAddTextToDisplay *AddTextToDisplay = NULL;
TIdBytes ucBuffer;
int offset = 0;
while (ReadMessageData(Buffer, offset, ucBuffer))
{
String sTemp = String().sprintf(_D("%d: ucBuffer.Length = %d ucBuffer = %s"), index, ucBuffer.Length, ToHex(ucBuffer).c_str());
if (AddTextToDisplay)
AddTextToDisplay->setTextToAdd(sTemp);
else
AddTextToDisplay = new TAddTextToDisplay(sTemp);
AddTextToDisplay->Synchronize();
++index;
}
delete AddTextToDisplay;
if (offset > 0)
Buffer->Remove(offset);
}
В противном случае, если вы хотите сделать свой собственный ввод-вывод сокета, вам придется использовать TIdTCPServer
и TIdTCPClient
напрямую вместо использования TIdMappedPortTCP
.
Большое спасибо. Здесь полночь. Я поработаю над этим завтра утром и дам вам знать, как все идет.
Решение работает как шарм, а это значит, что оно отзывчиво (одно сообщение, один ответ) и не зависает при попытке его закрыть. Еще раз большое спасибо.
Есть небольшая ошибка, нужна эта строка: UnicodeString sTemp; Но переполнение стека говорит, что банкоматов слишком много запросов на редактирование, поэтому я не могу его добавить.
@Beto я исправил ошибку
после изменения решения в соответствии с моими конкретными потребностями я обнаружил, что приложение снова зависает. Но я не думаю, что это из-за Indy, а из-за C++ Builder. Не могли бы вы взглянуть? Если да, было бы лучше задать новый вопрос или обновить этот?
@Beto это должно быть опубликовано как новый вопрос.
Ну, во-первых, ваш обработчик событий
OnExecute
работает в рабочем потоке, поэтому вы должны синхронизироваться с основным потоком пользовательского интерфейса при вызовеDisplay->Lines->Add()
, иначе могут произойти плохие вещи.