I am working on an application that works as a "man in the middle" to analyze a protocol (ISO 8583) sent over TCP/IP.
The main idea is to get the raw binary data and convert it to a string for parsing and decoding the protocol.
For this, I am using the TIdMappedPortTCP
component.
I am testing with Hercules.
I am working with:
Windows 11 Home
Embarcadero® C++Builder 10.4 Version 27.0.40680.4203
Delphi and C++ Builder 10.4 Update 2
Indy 10.6.2.0
More context can be found in these questions:
Where can I find a fully working example of a TCP Client and Server for Indy in C++Builder?
Parsing bytes as BCD with Indy C++ Builder
The problem is that I have to send the message twice to trigger the OnExecute
event. I think this might be length related but I haven't found the issue. Other than that the program does what is expected from it.
If I use this data in Hercules:
00 04 60 02
equivalent to:
"\x00\x04\x60\x02"
My program processes everything correctly:
Here is the code:
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++;
}
Here is the code for the functions called there. By the way, is there a better way to convert the bytes to a hex string?
// 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;
}
UPDATE I have created a class to update the TEditRich control. But the problem persist, I need to send the message twice to be processed and the application freezes when trying to close it. This is my class:
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;
}
};
And this is how my new OnExecute event looks:
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++;
}
}
You really should not be reading from the IOHandler
directly at all. You are getting your communication out of sync. TIdMappedPortTCP
internally reads from the client before firing the OnExecute
event, and reads from the target server before firing the OnOutboundData
event. In both cases, the bytes received are made available in the TIdMappedPortContext::NetData
property, which you are not processing at all.
You need to do all of your parsing using just the NetData
only, iterating through its bytes looking for complete messages, and saving incomplete messages for future events to finish.
Try something more like this instead:
#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);
}
Otherwise, if you want to do your own socket I/O, then you will have to use TIdTCPServer
and TIdTCPClient
directly instead of using TIdMappedPortTCP
.