376 lines
12 KiB
MQL5
376 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_START 3 // Deprecated group start
|
||
|
|
#define PB_END 4 // Deprecated group end
|
||
|
|
#define PB_FIXED32 5 // Four byte value
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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; }
|
||
|
|
int Pos(void) const { return m_pos; }
|
||
|
|
int End(void) const { return m_end; }
|
||
|
|
int Size(void) const { return ArraySize(m_buf); }
|
||
|
|
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
|
||
|
|
//+------------------------------------------------------------------+
|