diff --git a/CMakeLists.txt b/CMakeLists.txt index 80bf376..2a1339e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ if(CMAKE_TESTING_ENABLED) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.1.0 + GIT_TAG v3.5.3 ) FetchContent_MakeAvailable(Catch2) diff --git a/Decoder.hpp b/Decoder.hpp index 02aafcc..2f0169c 100644 --- a/Decoder.hpp +++ b/Decoder.hpp @@ -14,6 +14,9 @@ #pragma once #include +#include +#include + namespace ZCMessagePack { template @@ -37,27 +40,55 @@ class Maybe T m_value; }; -class Decoder + +class MemoryReader { public: - Decoder(const uint8_t * f_borrow_messageBuffer, uint8_t f_messageSize) : - m_messageBuffer(f_borrow_messageBuffer), + MemoryReader(const uint8_t * f_messageBuffer) : + buffer(f_messageBuffer) + { + } + + void read(uint8_t f_offset, uint8_t f_size, uint8_t * f_out_buffer ) const + { + std::memcpy(f_out_buffer, buffer + f_offset, f_size); + } + private: + const uint8_t * buffer; +}; + +template +class GenericDecoder +{ + + public: + // Constructs a new decoder that reads message data from the given message + // reader. The message reader must provide a read function with the following signature: + // void read(uint8_t f_offset, uint8_t f_size, uint8_t * f_out_buffer) const + // where f_offset is the offset in the message buffer, f_size is the number of bytes to read, f_out_buffer is the buffer to write the read data to. + GenericDecoder(RawMessageReader f_raw_message_reader, uint8_t f_messageSize) : + m_raw_message_reader(f_raw_message_reader), m_messageSize(f_messageSize) { } - + // Constructs a non-Generic Decoder using MemoryReader as the RawMessageReader. + template + GenericDecoder(const uint8_t * f_borrow_messageBuffer, uint8_t f_messageSize, typename std::enable_if::value>::type* = 0) : + m_raw_message_reader(MemoryReader(f_borrow_messageBuffer)), m_messageSize(f_messageSize) + { + } //--------------------------------------------------------------------- /// The following functions navigate the message: /// Returns a new decoder which is seeked to the map value, matching given key. - /// Returned Decoder will refer to the map value (not the key). - Decoder operator[](const char * f_mapKey) const; + /// Returned GenericDecoder will refer to the map value (not the key). + GenericDecoder operator[](const char * f_mapKey) const; /// Returns a new decoder which is seeked to the given array index. /// If f_index is out of range, the decoder will become invalid. /// NOTE: cannot overload operator[] for array access as implicit conversion is performed from int literal to char * whcih makes it ambiguous - Decoder accessArray(uint8_t f_index) const; + GenericDecoder accessArray(uint8_t f_index) const; /// Resets decoder position to the message root element. /// This will recover from an invalid decoder state. @@ -76,14 +107,14 @@ class Decoder /// Retrive both the Key and the Value of a map entry at given index. /// @param f_index given index of the map entry. If out of range, an - /// invalid Decoder is returned + /// invalid GenericDecoder is returned /// @param f_out_key user-allocated buffer where the key string will be /// written to. /// If a string is written (Valid decoder returned) /// it is always null terminated. /// @param f_maxSize Maximum number of bytes to write to f_out_key /// (including null termination) - Decoder getMapEntryByIndex(uint8_t f_index, char * f_out_key, uint8_t f_maxSize); + GenericDecoder getMapEntryByIndex(uint8_t f_index, char * f_out_key, uint8_t f_maxSize); //--------------------------------------------------------------------- @@ -141,6 +172,16 @@ class Decoder /// @param f_out_data buffer to which data is written. /// @returns number of bytes read if read was successful Maybe getBinary(uint8_t * f_out_data, uint8_t f_maxSize) const; + + /// Reads a Byte buffer from the MessagePack at current seek position. + /// @param writer writer instance which will be used to write the binary data to. + /// The writer must provide a write function with the following signature: + /// bool write(uint8_t data) + /// returning false if data could not be written + /// it is called for every byte to write + /// @returns number of bytes read and written to writer if read was successful + template + Maybe getBinary(Writer & writer) const; //--------------------------------------------------------------------- private: @@ -165,17 +206,27 @@ class Decoder HeaderInfo decodeHeader() const; + uint8_t readRawByte(uint8_t offset) const; + void seekNextElement(); /// Set decoder position to map element with given index. - /// Only works if current seek position is at a map, otherwise Decoder + /// Only works if current seek position is at a map, otherwise GenericDecoder /// is set to invalid seek. /// After successful seek, key can be read first void seekMapEntryByIndex(uint8_t f_index); - const uint8_t * m_messageBuffer; + RawMessageReader m_raw_message_reader; uint8_t m_messageSize; uint8_t m_position = 0; uint8_t m_validSeek = true; }; + +// Convenience typedef for a non-Generic Decoder using MemoryReader as the RawMessageReader. +// You can use the special constructor to create a non-Generic Decoder using MemoryReader as the RawMessageReader. +using Decoder = GenericDecoder; + + } + +#include "Decoder_impl.hpp" diff --git a/Decoder.cpp b/Decoder_impl.hpp similarity index 71% rename from Decoder.cpp rename to Decoder_impl.hpp index db4e840..3fc9259 100644 --- a/Decoder.cpp +++ b/Decoder_impl.hpp @@ -12,26 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "Decoder.hpp" +#pragma once +#include +#include #include +#include "Decoder.hpp" namespace ZCMessagePack { -Decoder Decoder::operator[](const char * f_mapKey) const +template +GenericDecoder GenericDecoder::operator[](const char * f_mapKey) const { - Decoder newDecoder = *this; - newDecoder.seekElementByKey(f_mapKey); - return newDecoder; + GenericDecoder newGenericDecoder = *this; + newGenericDecoder.seekElementByKey(f_mapKey); + return newGenericDecoder; } -Decoder Decoder::accessArray(uint8_t f_index) const +template +GenericDecoder GenericDecoder::accessArray(uint8_t f_index) const { - Decoder newDecoder = *this; - newDecoder.seekElementByIndex(f_index); - return newDecoder; + GenericDecoder newGenericDecoder = *this; + newGenericDecoder.seekElementByIndex(f_index); + return newGenericDecoder; } -void Decoder::seekElementByIndex(uint8_t f_index) +template +void GenericDecoder::seekElementByIndex(uint8_t f_index) { if(not m_validSeek) { @@ -59,21 +65,23 @@ void Decoder::seekElementByIndex(uint8_t f_index) return; } -Decoder Decoder::getMapEntryByIndex(uint8_t f_index, char * f_out_key, uint8_t f_maxSize) +template +GenericDecoder GenericDecoder::getMapEntryByIndex(uint8_t f_index, char * f_out_key, uint8_t f_maxSize) { - Decoder newDecoder = *this; - newDecoder.seekMapEntryByIndex(f_index); - auto len = newDecoder.getString(f_out_key, f_maxSize); + GenericDecoder newGenericDecoder = *this; + newGenericDecoder.seekMapEntryByIndex(f_index); + auto len = newGenericDecoder.getString(f_out_key, f_maxSize); if(not len.isValid()) { - newDecoder.m_validSeek = false; - return newDecoder; + newGenericDecoder.m_validSeek = false; + return newGenericDecoder; } - newDecoder.seekNextElement(); - return newDecoder; + newGenericDecoder.seekNextElement(); + return newGenericDecoder; } -void Decoder::seekMapEntryByIndex(uint8_t f_index) +template +void GenericDecoder::seekMapEntryByIndex(uint8_t f_index) { if(not m_validSeek) { @@ -103,7 +111,8 @@ void Decoder::seekMapEntryByIndex(uint8_t f_index) return; } -Maybe Decoder::getMapSize() const +template +Maybe GenericDecoder::getMapSize() const { if(not m_validSeek) { @@ -118,7 +127,8 @@ Maybe Decoder::getMapSize() const return Maybe(header.numPayloadElements); } -Maybe Decoder::getArraySize() const +template +Maybe GenericDecoder::getArraySize() const { if(not m_validSeek) { @@ -133,7 +143,8 @@ Maybe Decoder::getArraySize() const return Maybe(header.numPayloadElements); } -void Decoder::seekElementByKey(const char * f_key) +template +void GenericDecoder::seekElementByKey(const char * f_key) { if(not m_validSeek) { @@ -175,7 +186,8 @@ void Decoder::seekElementByKey(const char * f_key) return; } -void Decoder::seekNextElement() +template +void GenericDecoder::seekNextElement() { HeaderInfo header = decodeHeader(); switch(header.headerType) @@ -210,14 +222,15 @@ void Decoder::seekNextElement() } -Decoder::HeaderInfo Decoder::decodeHeader() const +template +typename GenericDecoder::HeaderInfo GenericDecoder::decodeHeader() const { HeaderInfo newHeaderInfo; if(m_position >= m_messageSize) { return newHeaderInfo; } - uint8_t typeCode = m_messageBuffer[m_position]; + uint8_t typeCode = readRawByte(m_position); newHeaderInfo.headerSize = 1; newHeaderInfo.numPayloadElements = 0; @@ -298,7 +311,7 @@ Decoder::HeaderInfo Decoder::decodeHeader() const } newHeaderInfo.headerType = HeaderInfo::String; newHeaderInfo.headerSize = 2; - newHeaderInfo.numPayloadElements = m_messageBuffer[m_position + 1]; + newHeaderInfo.numPayloadElements = readRawByte(m_position + 1); return newHeaderInfo; case 0xdc: if(m_messageSize - m_position < 3) @@ -307,7 +320,7 @@ Decoder::HeaderInfo Decoder::decodeHeader() const } newHeaderInfo.headerType = HeaderInfo::Array; newHeaderInfo.headerSize = 3; - newHeaderInfo.numPayloadElements = (static_cast(m_messageBuffer[m_position + 1]) << 8) | m_messageBuffer[m_position + 2]; + newHeaderInfo.numPayloadElements = (static_cast(readRawByte(m_position + 1)) << 8) | readRawByte(m_position + 2); return newHeaderInfo; case 0xde: if(m_messageSize - m_position < 3) @@ -316,7 +329,7 @@ Decoder::HeaderInfo Decoder::decodeHeader() const } newHeaderInfo.headerType = HeaderInfo::Map; newHeaderInfo.headerSize = 3; - newHeaderInfo.numPayloadElements = (static_cast(m_messageBuffer[m_position + 1]) << 8) | m_messageBuffer[m_position + 2]; + newHeaderInfo.numPayloadElements = (static_cast(readRawByte(m_position + 1)) << 8) | readRawByte(m_position + 2); return newHeaderInfo; } @@ -324,7 +337,8 @@ Decoder::HeaderInfo Decoder::decodeHeader() const } -Maybe Decoder::isNil() const +template +Maybe GenericDecoder::isNil() const { HeaderInfo header = decodeHeader(); if(header.headerType == HeaderInfo::Nil) @@ -342,7 +356,8 @@ Maybe Decoder::isNil() const } } -Maybe Decoder::getBool() const +template +Maybe GenericDecoder::getBool() const { HeaderInfo header = decodeHeader(); if(header.headerType == HeaderInfo::True) @@ -360,7 +375,8 @@ Maybe Decoder::getBool() const } } -Maybe Decoder::getUint32() const +template +Maybe GenericDecoder::getUint32() const { HeaderInfo header = decodeHeader(); if( @@ -375,20 +391,21 @@ Maybe Decoder::getUint32() const switch(header.numPayloadElements) { case 0: - return Maybe(m_messageBuffer[m_position] & 0x7f); + return Maybe(readRawByte(m_position) & 0x7f); case 1: - return Maybe(m_messageBuffer[m_position + 1]); + return Maybe(readRawByte(m_position + 1)); case 2: - return Maybe(static_cast(m_messageBuffer[m_position + 1]) << 8 | m_messageBuffer[m_position + 2]); + return Maybe(static_cast(readRawByte(m_position + 1)) << 8 | readRawByte(m_position + 2)); case 4: - return Maybe(static_cast(m_messageBuffer[m_position + 1]) << 24 | static_cast(m_messageBuffer[m_position + 2]) << 16 | static_cast(m_messageBuffer[m_position + 3]) << 8 | m_messageBuffer[m_position + 4]); + return Maybe(static_cast(readRawByte(m_position + 1)) << 24 | static_cast(readRawByte(m_position + 2)) << 16 | static_cast(readRawByte(m_position + 3)) << 8 | readRawByte(m_position + 4)); default: // number too big return Maybe(); } } -Maybe Decoder::getUint8() const +template +Maybe GenericDecoder::getUint8() const { auto u32val = getUint32(); if((not u32val.isValid()) or u32val.get() > 0xff) @@ -401,7 +418,8 @@ Maybe Decoder::getUint8() const } } -Maybe Decoder::getUint16() const +template +Maybe GenericDecoder::getUint16() const { auto u32val = getUint32(); if((not u32val.isValid()) or u32val.get() > 0xffff) @@ -414,7 +432,8 @@ Maybe Decoder::getUint16() const } } -Maybe Decoder::getString(char * f_out_data, uint8_t f_maxSize) const +template +Maybe GenericDecoder::getString(char * f_out_data, uint8_t f_maxSize) const { if(f_maxSize < 1) { @@ -432,7 +451,8 @@ Maybe Decoder::getString(char * f_out_data, uint8_t f_maxSize) const return numBytes; } -Maybe Decoder::compareString(const char * f_string) const +template +Maybe GenericDecoder::compareString(const char * f_string) const { HeaderInfo header = decodeHeader(); if( @@ -445,11 +465,11 @@ Maybe Decoder::compareString(const char * f_string) const return Maybe(); } - const char * message = reinterpret_cast(&m_messageBuffer[m_position + header.headerSize]); size_t i = 0; for(; i < header.numPayloadElements; i++) { - if(f_string[i] == '\0' or message[i] != f_string[i]) + char stored_char = static_cast(readRawByte(m_position + header.headerSize + i)); + if(f_string[i] == '\0' or stored_char != f_string[i]) { return Maybe(false); } @@ -461,7 +481,8 @@ Maybe Decoder::compareString(const char * f_string) const return Maybe(true); } -Maybe Decoder::getBinary(uint8_t * f_out_data, uint8_t f_maxSize) const +template +Maybe GenericDecoder::getBinary(uint8_t * f_out_data, uint8_t f_maxSize) const { HeaderInfo header = decodeHeader(); if( @@ -476,12 +497,39 @@ Maybe Decoder::getBinary(uint8_t * f_out_data, uint8_t f_maxSize) cons return Maybe(); } - memcpy(f_out_data, &m_messageBuffer[m_position + header.headerSize], header.numPayloadElements); + m_raw_message_reader.read(m_position + header.headerSize, header.numPayloadElements, f_out_data); return Maybe(header.numPayloadElements); } +template +template +Maybe GenericDecoder::getBinary(Writer & writer) const +{ + HeaderInfo header = decodeHeader(); + if( + header.headerType != HeaderInfo::String + or + m_position + header.headerSize + header.numPayloadElements > m_messageSize + ) + { + // type mismatch + return Maybe(); + } -bool Decoder::isValid() + for(uint16_t i = 0; i < header.numPayloadElements; i++) + { + bool success = writer.write(readRawByte(m_position + header.headerSize + i)); + if(not success) + { + return Maybe(); + } + } + return Maybe(header.numPayloadElements); +} + + +template +bool GenericDecoder::isValid() { auto header = decodeHeader(); if(header.headerType == HeaderInfo::InvalidHeader) @@ -490,4 +538,13 @@ bool Decoder::isValid() } return m_validSeek; } + +template +uint8_t ZCMessagePack::GenericDecoder::readRawByte(uint8_t offset) const +{ + uint8_t result; + m_raw_message_reader.read(offset, 1, &result); + return result; +} + } diff --git a/Encoder.hpp b/Encoder.hpp index af1a066..0ea0c5c 100644 --- a/Encoder.hpp +++ b/Encoder.hpp @@ -24,6 +24,8 @@ class Encoder /// Constructs the encoder. /// The given buffer f_out_borrow_messageBuffer will be used to write /// encoded data to. + /// NOTE: The Encoder does not yet support generic memory backend. + /// (The Decoder does support it) Encoder(uint8_t * f_out_borrow_messageBuffer, uint8_t f_bufferSize); /// Encodes an unsigned integer into the buffer. diff --git a/test/decoderBinaryWriterTest.cpp b/test/decoderBinaryWriterTest.cpp new file mode 100644 index 0000000..72791fa --- /dev/null +++ b/test/decoderBinaryWriterTest.cpp @@ -0,0 +1,88 @@ +// Copyright 2021 Rainer Schoenberger +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "Decoder.hpp" +#include "Encoder.hpp" + +#include + +using namespace ZCMessagePack; + +// Define a mock writer class for testing +class MockWriter { +public: + bool write(uint8_t data) { + buffer.push_back(data); + if(buffer.size() == max_size) { + return false; + } + return true; + } + std::vector buffer; + uint8_t max_size = 0; +}; + +struct TestFixture { + uint8_t binary[2] = {56, 67}; + uint8_t encoded_message[4]; + + TestFixture() { + Encoder encoder(encoded_message, sizeof(encoded_message)); + auto result = encoder.addBinary(binary, sizeof(binary)); + REQUIRE(result == true); + REQUIRE(encoder.getMessageSize() == 4); + } + + ~TestFixture() { + // Any cleanup code goes here + } +}; + +TEST_CASE_METHOD(TestFixture, "Successful decode", "") { + Decoder decoder(encoded_message, 4); + + MockWriter writer; + auto result = decoder.getBinary(writer); + + REQUIRE(result.isValid() == true); + REQUIRE(result.get() == 2); + REQUIRE(writer.buffer.size() == 2); + REQUIRE(writer.buffer[0] == 56); + REQUIRE(writer.buffer[1] == 67); +} + +TEST_CASE_METHOD(TestFixture, "writer cannot write", "") { + Decoder decoder(encoded_message, 4); + + MockWriter writer; + writer.max_size = 1; + auto result = decoder.getBinary(writer); + + REQUIRE(result.isValid() == false); + REQUIRE(writer.buffer.size() == 1); +} + +TEST_CASE_METHOD(TestFixture, "corrupt header", "") { + encoded_message[0] = 0xC1; + Decoder decoder(encoded_message, 4); + + MockWriter writer; + writer.max_size = 1; + auto result = decoder.getBinary(writer); + + REQUIRE(result.isValid() == false); + REQUIRE(writer.buffer.size() == 0); +} \ No newline at end of file