371 lines
12 KiB
MQL5
371 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| OMV Protobuf.mqh |
| |||
//| Copyright 2026, Allan Munene Mutiiria. |
| |||
//| https://t.me/Forex_Algo_Trader |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "Copyright 2026, Allan Munene Mutiiria."
| |||
#property link "https://t.me/Forex_Algo_Trader"
| |||
| |||
#ifndef OMV_PROTOBUF_MQH
| |||
#define OMV_PROTOBUF_MQH
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Wire type constants |
| |||
//+------------------------------------------------------------------+
| |||
#define PB_VARINT 0 // Variable length integer
| |||
#define PB_FIXED64 1 // Eight byte value
| |||
#define PB_LENGTH 2 // Length prefixed bytes
| |||
#define PB_FIXED32 5 // Four byte value, three and four being retired groups
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Four byte float overlay |
| |||
//+------------------------------------------------------------------+
| |||
union PbFloatBits
| |||
{
| |||
// Hold the raw stored bits
| |||
uint bits;
| |||
// Read the same bits as a number
| |||
float value;
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Eight byte double overlay |
| |||
//+------------------------------------------------------------------+
| |||
union PbDoubleBits
| |||
{
| |||
// Hold the raw stored bits
| |||
ulong bits;
| |||
// Read the same bits as a number
| |||
double value;
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Protobuf byte cursor |
| |||
//+------------------------------------------------------------------+
| |||
class CPbReader
| |||
{
| |||
private:
| |||
// Store the bytes being read
| |||
uchar m_buf[];
| |||
// Track the cursor position
| |||
int m_pos;
| |||
// Mark one past the last readable byte
| |||
int m_end;
| |||
// Flag a read that ran past the end
| |||
bool m_failed;
| |||
| |||
public:
| |||
CPbReader(void);
| |||
bool Attach(const uchar &src[], const int from, const int to);
| |||
bool LoadFile(const string path);
| |||
| |||
bool Eof(void) const { return m_pos >= m_end || m_failed; }
| |||
bool Failed(void) const { return m_failed; }
| |||
void Bytes(uchar &out[]) const { ArrayCopy(out, m_buf); }
| |||
| |||
ulong ReadVarint(void);
| |||
bool ReadTag(int &field, int &wire);
| |||
bool ReadString(string &out);
| |||
bool ReadBlock(int &from, int &to);
| |||
uint ReadFixed32(void);
| |||
ulong ReadFixed64(void);
| |||
float ReadFloat(void);
| |||
double ReadDouble(void);
| |||
bool SkipField(const int wire);
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Construct an empty reader |
| |||
//+------------------------------------------------------------------+
| |||
CPbReader::CPbReader(void)
| |||
{
| |||
//--- Set the cursor to an empty range
| |||
m_pos = 0;
| |||
m_end = 0;
| |||
//--- Clear the failure flag
| |||
m_failed = false;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Attach the cursor to a slice of an existing buffer |
| |||
//+------------------------------------------------------------------+
| |||
bool CPbReader::Attach(const uchar &src[], const int from, const int to)
| |||
{
| |||
//--- Get the source length
| |||
const int n = ArraySize(src);
| |||
//--- Reject a range the source cannot satisfy
| |||
if(from < 0 || to > n || from > to)
| |||
{
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
//--- Size the buffer to the requested slice
| |||
ArrayResize(m_buf, to - from);
| |||
//--- Copy every byte of the slice
| |||
for(int i = from; i < to; i++)
| |||
m_buf[i - from] = src[i];
| |||
//--- Reset the cursor over the copied slice
| |||
m_pos = 0;
| |||
m_end = to - from;
| |||
m_failed = false;
| |||
//--- Report success
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read a whole file from the terminal Files folder |
| |||
//+------------------------------------------------------------------+
| |||
bool CPbReader::LoadFile(const string path)
| |||
{
| |||
//--- Open the file in binary mode
| |||
const int handle = FileOpen(path, FILE_READ | FILE_BIN);
| |||
//--- Abort when the file cannot be opened
| |||
if(handle == INVALID_HANDLE)
| |||
{
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
//--- Measure the file
| |||
const int size = (int)FileSize(handle);
| |||
//--- Abort on an empty file
| |||
if(size <= 0)
| |||
{
| |||
FileClose(handle);
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
//--- Size the buffer to the whole file
| |||
ArrayResize(m_buf, size);
| |||
//--- Read every byte in one call
| |||
const int got = (int)FileReadArray(handle, m_buf, 0, size);
| |||
//--- Close before parsing anything
| |||
FileClose(handle);
| |||
//--- Abort on a short read
| |||
if(got != size)
| |||
{
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
//--- Reset the cursor over the loaded bytes
| |||
m_pos = 0;
| |||
m_end = size;
| |||
m_failed = false;
| |||
//--- Report success
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read a base 128 variable length integer |
| |||
//+------------------------------------------------------------------+
| |||
ulong CPbReader::ReadVarint(void)
| |||
{
| |||
//--- Start the accumulator and the bit offset
| |||
ulong value = 0;
| |||
int shift = 0;
| |||
//--- Consume bytes until the continuation bit clears
| |||
while(m_pos < m_end)
| |||
{
| |||
//--- Take the next byte
| |||
const uchar b = m_buf[m_pos++];
| |||
//--- Add its seven payload bits at the current offset
| |||
value |= ((ulong)(b & 0x7F)) << shift;
| |||
//--- Return once the high bit signals the last byte
| |||
if((b & 0x80) == 0)
| |||
return value;
| |||
//--- Advance to the next seven bit group
| |||
shift += 7;
| |||
//--- Fail beyond the width of a sixty four bit value
| |||
if(shift > 63)
| |||
{
| |||
m_failed = true;
| |||
return 0;
| |||
}
| |||
}
| |||
//--- Fail when the buffer ends mid number
| |||
m_failed = true;
| |||
return 0;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read a field tag into its number and wire type |
| |||
//+------------------------------------------------------------------+
| |||
bool CPbReader::ReadTag(int &field, int &wire)
| |||
{
| |||
//--- Clear both outputs
| |||
field = 0;
| |||
wire = 0;
| |||
//--- Report the end of the buffer without failing
| |||
if(m_pos >= m_end)
| |||
return false;
| |||
//--- Read the packed tag
| |||
const ulong tag = ReadVarint();
| |||
//--- Abort on a broken read
| |||
if(m_failed)
| |||
return false;
| |||
//--- Split the low three bits as the wire type
| |||
wire = (int)(tag & 0x07);
| |||
//--- Take the remaining bits as the field number
| |||
field = (int)(tag >> 3);
| |||
//--- Report a usable field number
| |||
return field > 0;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read a length prefixed field as text |
| |||
//+------------------------------------------------------------------+
| |||
bool CPbReader::ReadString(string &out)
| |||
{
| |||
//--- Clear the output
| |||
out = "";
| |||
//--- Read the byte count
| |||
const int len = (int)ReadVarint();
| |||
//--- Abort when the count is broken or runs past the end
| |||
if(m_failed || len < 0 || m_pos + len > m_end)
| |||
{
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
//--- Accept an empty string
| |||
if(len == 0)
| |||
return true;
| |||
//--- Copy the payload into a temporary array
| |||
uchar tmp[];
| |||
ArrayResize(tmp, len);
| |||
for(int i = 0; i < len; i++)
| |||
tmp[i] = m_buf[m_pos + i];
| |||
//--- Step the cursor past the payload
| |||
m_pos += len;
| |||
//--- Decode the payload as UTF-8
| |||
out = CharArrayToString(tmp, 0, len, CP_UTF8);
| |||
//--- Report success
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Locate a nested message and step over it |
| |||
//+------------------------------------------------------------------+
| |||
bool CPbReader::ReadBlock(int &from, int &to)
| |||
{
| |||
//--- Clear both outputs
| |||
from = 0;
| |||
to = 0;
| |||
//--- Read the byte count
| |||
const int len = (int)ReadVarint();
| |||
//--- Abort when the count is broken or runs past the end
| |||
if(m_failed || len < 0 || m_pos + len > m_end)
| |||
{
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
//--- Report where the payload starts and ends
| |||
from = m_pos;
| |||
to = m_pos + len;
| |||
//--- Step the cursor past the payload
| |||
m_pos = to;
| |||
//--- Report success
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read four bytes least significant first |
| |||
//+------------------------------------------------------------------+
| |||
uint CPbReader::ReadFixed32(void)
| |||
{
| |||
//--- Fail when four bytes are not available
| |||
if(m_pos + 4 > m_end)
| |||
{
| |||
m_failed = true;
| |||
return 0;
| |||
}
| |||
//--- Assemble the value one byte at a time
| |||
uint value = 0;
| |||
for(int i = 0; i < 4; i++)
| |||
value |= ((uint)m_buf[m_pos + i]) << (8 * i);
| |||
//--- Step the cursor past the value
| |||
m_pos += 4;
| |||
//--- Return the assembled value
| |||
return value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read eight bytes least significant first |
| |||
//+------------------------------------------------------------------+
| |||
ulong CPbReader::ReadFixed64(void)
| |||
{
| |||
//--- Fail when eight bytes are not available
| |||
if(m_pos + 8 > m_end)
| |||
{
| |||
m_failed = true;
| |||
return 0;
| |||
}
| |||
//--- Assemble the value one byte at a time
| |||
ulong value = 0;
| |||
for(int i = 0; i < 8; i++)
| |||
value |= ((ulong)m_buf[m_pos + i]) << (8 * i);
| |||
//--- Step the cursor past the value
| |||
m_pos += 8;
| |||
//--- Return the assembled value
| |||
return value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read four bytes as a float |
| |||
//+------------------------------------------------------------------+
| |||
float CPbReader::ReadFloat(void)
| |||
{
| |||
//--- Load the raw bits into the overlay
| |||
PbFloatBits cast;
| |||
cast.bits = ReadFixed32();
| |||
//--- Return zero on a broken read
| |||
if(m_failed)
| |||
return 0.0f;
| |||
//--- Return the same bits read as a number
| |||
return cast.value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Read eight bytes as a double |
| |||
//+------------------------------------------------------------------+
| |||
double CPbReader::ReadDouble(void)
| |||
{
| |||
//--- Load the raw bits into the overlay
| |||
PbDoubleBits cast;
| |||
cast.bits = ReadFixed64();
| |||
//--- Return zero on a broken read
| |||
if(m_failed)
| |||
return 0.0;
| |||
//--- Return the same bits read as a number
| |||
return cast.value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Step over an unwanted field |
| |||
//+------------------------------------------------------------------+
| |||
bool CPbReader::SkipField(const int wire)
| |||
{
| |||
//--- Consume the field according to its wire type
| |||
switch(wire)
| |||
{
| |||
case PB_VARINT:
| |||
ReadVarint();
| |||
return !m_failed;
| |||
case PB_FIXED64:
| |||
ReadFixed64();
| |||
return !m_failed;
| |||
case PB_FIXED32:
| |||
ReadFixed32();
| |||
return !m_failed;
| |||
case PB_LENGTH:
| |||
{
| |||
//--- Read the length and jump past the payload
| |||
int from, to;
| |||
return ReadBlock(from, to);
| |||
}
| |||
}
| |||
//--- Fail on a deprecated group marker
| |||
m_failed = true;
| |||
return false;
| |||
}
| |||
| |||
#endif // OMV_PROTOBUF_MQH
| |||
//+------------------------------------------------------------------+
|