Почему мне нужно отправить сообщение дважды, чтобы вызвать событие Indy OnExecute?

Я работаю над приложением, которое работает как «человек посередине» для анализа протокола (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++;
    }

}

Ну, во-первых, ваш обработчик событий OnExecute работает в рабочем потоке, поэтому вы должны синхронизироваться с основным потоком пользовательского интерфейса при вызове Display->Lines->Add(), иначе могут произойти плохие вещи.

Remy Lebeau 11.12.2022 22:13

Спасибо, я этого не знал. Кажется, это проблема, поскольку приложение не может выйти, оно зависает, когда я пытаюсь его закрыть.

Beto 11.12.2022 23:34

@RemyLebeau Я добавил новый класс и изменил событие OnExecute (см. обновление внизу), но проблема не устранена. Любая помощь будет высоко оценена.

Beto 12.12.2022 23:34
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы действительно не должны читать прямо из 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.

Большое спасибо. Здесь полночь. Я поработаю над этим завтра утром и дам вам знать, как все идет.

Beto 13.12.2022 01:11

Решение работает как шарм, а это значит, что оно отзывчиво (одно сообщение, один ответ) и не зависает при попытке его закрыть. Еще раз большое спасибо.

Beto 13.12.2022 12:59

Есть небольшая ошибка, нужна эта строка: UnicodeString sTemp; Но переполнение стека говорит, что банкоматов слишком много запросов на редактирование, поэтому я не могу его добавить.

Beto 13.12.2022 13:03

@Beto я исправил ошибку

Remy Lebeau 13.12.2022 18:09

после изменения решения в соответствии с моими конкретными потребностями я обнаружил, что приложение снова зависает. Но я не думаю, что это из-за Indy, а из-за C++ Builder. Не могли бы вы взглянуть? Если да, было бы лучше задать новый вопрос или обновить этот?

Beto 16.12.2022 21:43

@Beto это должно быть опубликовано как новый вопрос.

Remy Lebeau 16.12.2022 22:49

Другие вопросы по теме