diff --git a/components/core/src/ffi/ir_stream/decoding_methods.cpp b/components/core/src/ffi/ir_stream/decoding_methods.cpp index 167fc77cd..3ceb05ecc 100644 --- a/components/core/src/ffi/ir_stream/decoding_methods.cpp +++ b/components/core/src/ffi/ir_stream/decoding_methods.cpp @@ -1,5 +1,7 @@ #include "decoding_methods.hpp" +#include + #include "byteswap.hpp" #include "protocol_constants.hpp" @@ -251,6 +253,12 @@ parse_timestamp(ReaderInterface& reader, encoded_tag_t encoded_tag, epoch_time_m return IRErrorCode_Incomplete_IR; } ts = ts_delta; + } else if (cProtocol::Payload::TimestampDeltaLong == encoded_tag) { + int64_t ts_delta; + if (false == decode_int(reader, ts_delta)) { + return IRErrorCode_Incomplete_IR; + } + ts = ts_delta; } else { return IRErrorCode_Corrupted_IR; } @@ -277,9 +285,8 @@ generic_decode_next_message(ReaderInterface& reader, string& message, epoch_time message.append(value, begin_pos, length); }; - auto encoded_int_handler = [&](encoded_variable_t value) { - message.append(decode_integer_var(value)); - }; + auto encoded_int_handler + = [&](encoded_variable_t value) { message.append(decode_integer_var(value)); }; auto encoded_float_handler = [&](encoded_variable_t encoded_float) { message.append(decode_float_var(encoded_float)); @@ -458,6 +465,36 @@ IRErrorCode decode_preamble( return IRErrorCode_Success; } +IRProtocolErrorCode validate_protocol_version(std::string_view protocol_version) { + if ("v0.0.0" == protocol_version) { + // This version is hardcoded to support the oldest IR protocol version. + // When this version is no longer supported, this branch should be + // removed. + return IRProtocolErrorCode_Supported; + } + std::regex const protocol_version_regex{cProtocol::Metadata::VersionRegex}; + if (false + == std::regex_match( + protocol_version.begin(), + protocol_version.end(), + protocol_version_regex + )) + { + return IRProtocolErrorCode_Invalid; + } + std::string_view current_build_protocol_version{cProtocol::Metadata::VersionValue}; + auto get_major_version{[](std::string_view version) { + return version.substr(0, version.find('.')); + }}; + if (current_build_protocol_version < protocol_version) { + return IRProtocolErrorCode_Too_New; + } + if (get_major_version(current_build_protocol_version) > get_major_version(protocol_version)) { + return IRProtocolErrorCode_Too_Old; + } + return IRProtocolErrorCode_Supported; +} + namespace four_byte_encoding { IRErrorCode decode_next_message( ReaderInterface& reader, diff --git a/components/core/src/ffi/ir_stream/decoding_methods.hpp b/components/core/src/ffi/ir_stream/decoding_methods.hpp index dcf5c207d..000dee70f 100644 --- a/components/core/src/ffi/ir_stream/decoding_methods.hpp +++ b/components/core/src/ffi/ir_stream/decoding_methods.hpp @@ -18,6 +18,13 @@ typedef enum { IRErrorCode_Incomplete_IR, } IRErrorCode; +typedef enum { + IRProtocolErrorCode_Supported, + IRProtocolErrorCode_Too_Old, + IRProtocolErrorCode_Too_New, + IRProtocolErrorCode_Invalid, +} IRProtocolErrorCode; + class DecodingException : public TraceableException { public: // Constructors @@ -142,6 +149,20 @@ IRErrorCode decode_preamble( std::vector& metadata ); +/** + * Validates whether the given protocol version can be supported by the current + * build. + * @param protocol_version + * @return IRProtocolErrorCode_Supported if the protocol version is supported. + * @return IRProtocolErrorCode_Too_Old if the protocol version is no longer + * supported by this build's protocol version. + * @return IRProtocolErrorCode_Too_New if the protocol version is newer than this + * build's protocol version. + * @return IRProtocolErrorCode_Invalid if the protocol version does not follow + * the SemVer specification. + */ +IRProtocolErrorCode validate_protocol_version(std::string_view protocol_version); + namespace eight_byte_encoding { /** * Decodes the next message for the eight-byte encoding IR stream. diff --git a/components/core/src/ffi/ir_stream/encoding_methods.cpp b/components/core/src/ffi/ir_stream/encoding_methods.cpp index ec427ab76..35c766aef 100644 --- a/components/core/src/ffi/ir_stream/encoding_methods.cpp +++ b/components/core/src/ffi/ir_stream/encoding_methods.cpp @@ -57,10 +57,7 @@ static void add_base_metadata_fields( * @param logtype * @return true */ -static bool append_constant_to_logtype( - string_view constant, - string& logtype -); +static bool append_constant_to_logtype(string_view constant, string& logtype); /** * A functor for encoding dictionary variables in a message @@ -308,8 +305,11 @@ namespace four_byte_encoding { } else if (INT32_MIN <= timestamp_delta && timestamp_delta <= INT32_MAX) { ir_buf.push_back(cProtocol::Payload::TimestampDeltaInt); encode_int(static_cast(timestamp_delta), ir_buf); + } else if (INT64_MIN <= timestamp_delta && timestamp_delta <= INT64_MAX) { + ir_buf.push_back(cProtocol::Payload::TimestampDeltaLong); + encode_int(static_cast(timestamp_delta), ir_buf); } else { - // Delta exceeds maximum representable by an int (24.86 days) + // Delta exceeds maximum representable by a 64-bit int return false; } diff --git a/components/core/src/ffi/ir_stream/protocol_constants.hpp b/components/core/src/ffi/ir_stream/protocol_constants.hpp index 652c2a16a..418e32a44 100644 --- a/components/core/src/ffi/ir_stream/protocol_constants.hpp +++ b/components/core/src/ffi/ir_stream/protocol_constants.hpp @@ -12,7 +12,14 @@ namespace Metadata { constexpr int8_t LengthUShort = 0x12; constexpr char VersionKey[] = "VERSION"; - constexpr char VersionValue[] = "v0.0.0"; + constexpr char VersionValue[] = "0.0.1"; + + // The following regex can be used to validate a Semantic Versioning string. + // The source of the regex can be found here: https://semver.org/ + constexpr char VersionRegex[] = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)" + "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" + "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"; constexpr char TimestampPatternKey[] = "TIMESTAMP_PATTERN"; constexpr char TimestampPatternSyntaxKey[] = "TIMESTAMP_PATTERN_SYNTAX"; @@ -39,6 +46,7 @@ namespace Payload { constexpr int8_t TimestampDeltaByte = 0x31; constexpr int8_t TimestampDeltaShort = 0x32; constexpr int8_t TimestampDeltaInt = 0x33; + constexpr int8_t TimestampDeltaLong = 0x34; } // namespace Payload constexpr int8_t FourByteEncodingMagicNumber[] diff --git a/components/core/src/ir/LogEventDeserializer.cpp b/components/core/src/ir/LogEventDeserializer.cpp index 16ba71ba2..2f3adb517 100644 --- a/components/core/src/ir/LogEventDeserializer.cpp +++ b/components/core/src/ir/LogEventDeserializer.cpp @@ -37,8 +37,8 @@ auto LogEventDeserializer::create(ReaderInterface& reader) return std::errc::protocol_error; } auto metadata_version = version_iter->get_ref(); - if (static_cast(ffi::ir_stream::cProtocol::Metadata::VersionValue) - != metadata_version) + if (ffi::ir_stream::IRProtocolErrorCode_Supported + != ffi::ir_stream::validate_protocol_version(metadata_version)) { return std::errc::protocol_not_supported; } diff --git a/components/core/tests/test-ir_encoding_methods.cpp b/components/core/tests/test-ir_encoding_methods.cpp index c4d586d3e..53dc345c4 100644 --- a/components/core/tests/test-ir_encoding_methods.cpp +++ b/components/core/tests/test-ir_encoding_methods.cpp @@ -28,6 +28,7 @@ using ffi::ir_stream::decode_preamble; using ffi::ir_stream::encoded_tag_t; using ffi::ir_stream::get_encoding_type; using ffi::ir_stream::IRErrorCode; +using ffi::ir_stream::validate_protocol_version; using ffi::wildcard_query_matches_any_encoded_var; using ir::VariablePlaceholder; using std::chrono::duration_cast; @@ -129,8 +130,10 @@ static epoch_time_ms_t get_current_ts() { template bool match_encoding_type(bool is_four_bytes_encoding) { - static_assert(is_same_v || - is_same_v); + static_assert( + (is_same_v) + || (is_same_v) + ); if constexpr (is_same_v) { return false == is_four_bytes_encoding; @@ -141,8 +144,10 @@ bool match_encoding_type(bool is_four_bytes_encoding) { template epoch_time_ms_t get_next_timestamp_for_test() { - static_assert(is_same_v || - is_same_v); + static_assert( + (is_same_v) + || (is_same_v) + ); // We return an absolute timestamp for the eight-byte encoding and a mocked // timestamp delta for the four-byte encoding @@ -165,8 +170,10 @@ bool encode_preamble( epoch_time_ms_t reference_timestamp, vector& ir_buf ) { - static_assert(is_same_v || - is_same_v); + static_assert( + (is_same_v) + || (is_same_v) + ); if constexpr (is_same_v) { return ffi::ir_stream::eight_byte_encoding::encode_preamble( @@ -193,8 +200,10 @@ bool encode_message( string& logtype, vector& ir_buf ) { - static_assert(is_same_v || - is_same_v); + static_assert( + (is_same_v) + || (is_same_v) + ); if constexpr (is_same_v) { return ffi::ir_stream::eight_byte_encoding::encode_message( @@ -216,8 +225,10 @@ bool encode_message( template IRErrorCode decode_next_message(BufferReader& reader, string& message, epoch_time_ms_t& decoded_ts) { - static_assert(is_same_v || - is_same_v); + static_assert( + (is_same_v) + || (is_same_v) + ); if constexpr (is_same_v) { return ffi::ir_stream::eight_byte_encoding::decode_next_message( @@ -244,11 +255,13 @@ TEST_CASE("get_encoding_type", "[ffi][get_encoding_type]") { // Test eight-byte encoding vector eight_byte_encoding_vec{ EightByteEncodingMagicNumber, - EightByteEncodingMagicNumber + MagicNumberLength}; + EightByteEncodingMagicNumber + MagicNumberLength + }; BufferReader eight_byte_ir_buffer{ size_checked_pointer_cast(eight_byte_encoding_vec.data()), - eight_byte_encoding_vec.size()}; + eight_byte_encoding_vec.size() + }; REQUIRE(get_encoding_type(eight_byte_ir_buffer, is_four_bytes_encoding) == IRErrorCode::IRErrorCode_Success); REQUIRE(match_encoding_type(is_four_bytes_encoding)); @@ -256,11 +269,13 @@ TEST_CASE("get_encoding_type", "[ffi][get_encoding_type]") { // Test four-byte encoding vector four_byte_encoding_vec{ FourByteEncodingMagicNumber, - FourByteEncodingMagicNumber + MagicNumberLength}; + FourByteEncodingMagicNumber + MagicNumberLength + }; BufferReader four_byte_ir_buffer{ size_checked_pointer_cast(four_byte_encoding_vec.data()), - four_byte_encoding_vec.size()}; + four_byte_encoding_vec.size() + }; REQUIRE(get_encoding_type(four_byte_ir_buffer, is_four_bytes_encoding) == IRErrorCode::IRErrorCode_Success); REQUIRE(match_encoding_type(is_four_bytes_encoding)); @@ -275,7 +290,8 @@ TEST_CASE("get_encoding_type", "[ffi][get_encoding_type]") { BufferReader incomplete_buffer{ size_checked_pointer_cast(four_byte_encoding_vec.data()), - four_byte_encoding_vec.size() - 1}; + four_byte_encoding_vec.size() - 1 + }; REQUIRE(get_encoding_type(incomplete_buffer, is_four_bytes_encoding) == IRErrorCode::IRErrorCode_Incomplete_IR); @@ -283,7 +299,8 @@ TEST_CASE("get_encoding_type", "[ffi][get_encoding_type]") { vector const invalid_ir_vec{0x02, 0x43, 0x24, 0x34}; BufferReader invalid_ir_buffer{ size_checked_pointer_cast(invalid_ir_vec.data()), - invalid_ir_vec.size()}; + invalid_ir_vec.size() + }; REQUIRE(get_encoding_type(invalid_ir_buffer, is_four_bytes_encoding) == IRErrorCode::IRErrorCode_Corrupted_IR); } @@ -329,8 +346,8 @@ TEMPLATE_TEST_CASE( string_view json_metadata{metadata_ptr, metadata_size}; auto metadata_json = nlohmann::json::parse(json_metadata); - REQUIRE(ffi::ir_stream::cProtocol::Metadata::VersionValue - == metadata_json.at(ffi::ir_stream::cProtocol::Metadata::VersionKey)); + std::string const version = metadata_json.at(ffi::ir_stream::cProtocol::Metadata::VersionKey); + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Supported == validate_protocol_version(version)); REQUIRE(ffi::ir_stream::cProtocol::Metadata::EncodingJson == metadata_type); set_timestamp_info(metadata_json, ts_info); REQUIRE(timestamp_pattern_syntax == ts_info.timestamp_pattern_syntax); @@ -353,7 +370,8 @@ TEMPLATE_TEST_CASE( == IRErrorCode::IRErrorCode_Success); string_view json_metadata_copied{ size_checked_pointer_cast(json_metadata_vec.data()), - json_metadata_vec.size()}; + json_metadata_vec.size() + }; // Crosscheck with the json_metadata decoded previously REQUIRE(json_metadata_copied == json_metadata); @@ -361,7 +379,8 @@ TEMPLATE_TEST_CASE( ir_buf.resize(encoded_preamble_end_pos - 1); BufferReader incomplete_preamble_buffer{ size_checked_pointer_cast(ir_buf.data()), - ir_buf.size()}; + ir_buf.size() + }; incomplete_preamble_buffer.seek_from_begin(MagicNumberLength); REQUIRE(decode_preamble(incomplete_preamble_buffer, metadata_type, metadata_pos, metadata_size) == IRErrorCode::IRErrorCode_Incomplete_IR); @@ -370,7 +389,8 @@ TEMPLATE_TEST_CASE( ir_buf[MagicNumberLength] = 0x23; BufferReader corrupted_preamble_buffer{ size_checked_pointer_cast(ir_buf.data()), - ir_buf.size()}; + ir_buf.size() + }; REQUIRE(decode_preamble(corrupted_preamble_buffer, metadata_type, metadata_pos, metadata_size) == IRErrorCode::IRErrorCode_Corrupted_IR); } @@ -412,7 +432,8 @@ TEMPLATE_TEST_CASE( ir_buf.resize(encoded_message_end_pos - 4); BufferReader incomplete_preamble_buffer{ size_checked_pointer_cast(ir_buf.data()), - ir_buf.size()}; + ir_buf.size() + }; REQUIRE(IRErrorCode::IRErrorCode_Incomplete_IR == decode_next_message(incomplete_preamble_buffer, message, timestamp)); } @@ -446,7 +467,8 @@ TEST_CASE("message_decode_error", "[ffi][decode_next_message]") { ir_with_extra_escape.at(logtype_end_pos - 1) = ir::cVariablePlaceholderEscapeCharacter; BufferReader ir_with_extra_escape_buffer{ size_checked_pointer_cast(ir_with_extra_escape.data()), - ir_with_extra_escape.size()}; + ir_with_extra_escape.size() + }; REQUIRE(IRErrorCode::IRErrorCode_Decode_Error == decode_next_message( ir_with_extra_escape_buffer, @@ -460,7 +482,8 @@ TEST_CASE("message_decode_error", "[ffi][decode_next_message]") { = enum_to_underlying_type(VariablePlaceholder::Dictionary); BufferReader ir_with_extra_placeholder_buffer{ size_checked_pointer_cast(ir_with_extra_placeholder.data()), - ir_with_extra_placeholder.size()}; + ir_with_extra_placeholder.size() + }; REQUIRE(IRErrorCode::IRErrorCode_Decode_Error == decode_next_message( ir_with_extra_placeholder_buffer, @@ -469,32 +492,52 @@ TEST_CASE("message_decode_error", "[ffi][decode_next_message]") { )); } -TEST_CASE("decode_next_message_four_byte_negative_delta", "[ffi][decode_next_message]") { - string message = "Static <\text>, dictVar1, 123, 456345232.7234223, " - "dictVar2, 987, 654.3, end of static text"; +TEST_CASE("decode_next_message_four_byte_timestamp_delta", "[ffi][decode_next_message]") { + string const message = "Static <\text>, dictVar1, 123, 456345232.7234223, " + "dictVar2, 987, 654.3, end of static text"; + auto ts_delta = GENERATE( + static_cast(0), + static_cast(INT8_MIN), + static_cast(INT8_MIN + 1), + static_cast(INT8_MAX - 1), + static_cast(INT8_MAX), + static_cast(INT16_MIN), + static_cast(INT16_MIN + 1), + static_cast(INT16_MAX - 1), + static_cast(INT16_MAX), + static_cast(INT32_MIN), + static_cast(INT32_MIN + 1), + static_cast(INT32_MAX - 1), + static_cast(INT32_MAX), + static_cast(INT64_MIN), + static_cast(INT64_MAX) + ); vector ir_buf; string logtype; - - epoch_time_ms_t reference_delta_ts_negative = -5; - REQUIRE(true - == encode_message( - reference_delta_ts_negative, - message, - logtype, - ir_buf - )); + REQUIRE(encode_message(ts_delta, message, logtype, ir_buf)); BufferReader ir_buffer{size_checked_pointer_cast(ir_buf.data()), ir_buf.size()}; string decoded_message; - epoch_time_ms_t delta_ts; + epoch_time_ms_t decoded_delta_ts{}; REQUIRE(IRErrorCode::IRErrorCode_Success == decode_next_message( ir_buffer, decoded_message, - delta_ts + decoded_delta_ts )); REQUIRE(message == decoded_message); - REQUIRE(delta_ts == reference_delta_ts_negative); + REQUIRE(decoded_delta_ts == ts_delta); +} + +TEST_CASE("validate_protocol_version", "[ffi][validate_version_protocol]") { + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Invalid == validate_protocol_version("v0.0.1")); + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Invalid == validate_protocol_version("0.1")); + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Invalid == validate_protocol_version("0.a.1")); + + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Too_New == validate_protocol_version("1000.0.0")); + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Supported + == validate_protocol_version(ffi::ir_stream::cProtocol::Metadata::VersionValue)); + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Supported == validate_protocol_version("v0.0.0")); } TEMPLATE_TEST_CASE( @@ -541,7 +584,8 @@ TEMPLATE_TEST_CASE( BufferReader complete_ir_buffer{ size_checked_pointer_cast(ir_buf.data()), - ir_buf.size()}; + ir_buf.size() + }; bool is_four_bytes_encoding; REQUIRE(get_encoding_type(complete_ir_buffer, is_four_bytes_encoding) @@ -560,8 +604,8 @@ TEMPLATE_TEST_CASE( auto* json_metadata_ptr{size_checked_pointer_cast(ir_buf.data() + metadata_pos)}; string_view json_metadata{json_metadata_ptr, metadata_size}; auto metadata_json = nlohmann::json::parse(json_metadata); - REQUIRE(ffi::ir_stream::cProtocol::Metadata::VersionValue - == metadata_json.at(ffi::ir_stream::cProtocol::Metadata::VersionKey)); + string const version = metadata_json.at(ffi::ir_stream::cProtocol::Metadata::VersionKey); + REQUIRE(ffi::ir_stream::IRProtocolErrorCode_Supported == validate_protocol_version(version)); REQUIRE(ffi::ir_stream::cProtocol::Metadata::EncodingJson == metadata_type); set_timestamp_info(metadata_json, ts_info); REQUIRE(timestamp_pattern_syntax == ts_info.timestamp_pattern_syntax);