//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+