From: https://github.com/codership/galera/commits/4.x-26.4.16-gh-558 Forwarded: https://github.com/codership/galera/pull/588 From cb4437f7f4b30ff1d3394fa10fa1e4168b47b61c Mon Sep 17 00:00:00 2001 Author: Teemu Ollakka Date: Mon, 2 Oct 2023 14:48:06 +0300 Subject: [PATCH 1/8] Add CMake option to compile with UBSAN instrumentation CMake option -DGALERA_WITH_UBSAN:BOOL=ON enables UBSAN instrumentation for the build. --- cmake/asan.cmake | 6 ++++++ 1 file changed, 6 insertions(+) --- a/cmake/asan.cmake +++ b/cmake/asan.cmake @@ -1,5 +1,5 @@ # -# Copyright (C) 2020 Codership Oy +# Copyright (C) 2020-2023 Codership Oy # if (GALERA_WITH_ASAN) @@ -7,3 +7,21 @@ if (GALERA_WITH_ASAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") add_definitions(-DGALERA_WITH_ASAN) endif() + +if (GALERA_WITH_UBSAN) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") + add_definitions(-DGALERA_WITH_UBSAN) + # don't run unit tests that use outdaed unaligned record set format + add_definitions(-DGALERA_ONLY_ALIGNED) + + find_library(UBSAN_LIB NAMES ubsan libubsan.so.1) + message(STATUS ${UBSAN_LIB}) + set(CMAKE_REQUIRED_LIBRARIES ${UBSAN_LIB}) + check_c_source_compiles("int main() { return 0; }" GALERA_HAVE_UBSAN_LIB) + if (NOT GALERA_HAVE_UBSAN_LIB) + message(FATAL_ERROR "Could not find UBSAN support library") + endif() + unset(CMAKE_REQUIRED_LIBRARIES) + list(APPEND GALERA_SYSTEM_LIBS ${UBSAN_LIB}) +endif() --- a/cmake/os.cmake +++ b/cmake/os.cmake @@ -11,7 +11,8 @@ set(GALERA_SYSTEM_LIBS ${PTHREAD_LIB} ${ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") # Check if linkage with atomic library is needed for 8 byte atomics set(ATOMIC_8_TEST_C_SOURCE - "int main() { long long val; __atomic_fetch_add_8(&val, 1, __ATOMIC_SEQ_CST); return 0;}") + "#include + int main() { atomic_llong val; atomic_fetch_add(&val, 1); return 0; }") check_c_source_compiles("${ATOMIC_8_TEST_C_SOURCE}" GALERA_HAVE_ATOMIC) if (NOT GALERA_HAVE_ATOMIC) find_library(ATOMIC_LIB NAMES atomic libatomic.so.1) --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -1,5 +1,5 @@ # -# Copyright (C) 2020 Codership Oy +# Copyright (C) 2020-2023 Codership Oy # # Common compiler and preprocessor options. # @@ -46,7 +46,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} if (CMAKE_BUILD_TYPE STREQUAL "Debug") # To detect STD library misuse with Debug builds. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -O0") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") # Enable debug sync points add_definitions(-DGU_DBUG_ON) else() --- a/galera/tests/data_set_check.cpp +++ b/galera/tests/data_set_check.cpp @@ -1,10 +1,14 @@ -/* Copyright (C) 2013-2020 Codership Oy +/* Copyright (C) 2013-2023 Codership Oy * * $Id$ */ #undef NDEBUG +#if defined(__sun) +#define GALERA_ONLY_ALIGNED +#endif + #include "../src/data_set.hpp" #include "gu_logger.hpp" --- a/galera/tests/defaults_check.cpp +++ b/galera/tests/defaults_check.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2018-2020 Codership Oy +// Copyright (C) 2018-2023 Codership Oy // #include @@ -307,7 +307,6 @@ START_TEST(defaults) ret, strerror(ret)); } - provider.free(&provider); mark_point(); /* cleanup files */ --- a/galerautils/src/gu_crc32c_x86.c +++ b/galerautils/src/gu_crc32c_x86.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Codership Oy + * Copyright (C) 2020-2023 Codership Oy */ /** @@ -19,33 +19,77 @@ #include #include +/* process data preceding the first 4-aligned byte */ static inline gu_crc32c_t -crc32c_x86_tail3(gu_crc32c_t state, const uint8_t* ptr, size_t len) +crc32c_x86_head3(gu_crc32c_t state, const uint8_t* ptr, size_t len) { + assert(len > 0); assert(len < 4); - switch (len) + if (((uintptr_t)ptr) & 1) { - case 3: + /* odd address */ state = __builtin_ia32_crc32qi(state, *ptr); ptr++; - /* fall through */ + len--; + } + + /* here ptr is at least 2-aligned */ + if (len >= 2) + { + assert(0 == ((uintptr_t)ptr)%2); + state = __builtin_ia32_crc32hi(state, *(uint16_t*)ptr); + ptr += 2; + len -= 2; + } + + if (len) + { + assert(1 == len); + state = __builtin_ia32_crc32qi(state, *ptr); + } + + return state; +} + +static inline gu_crc32c_t +crc32c_x86_tail3(gu_crc32c_t state, const uint8_t* ptr, size_t len) +{ + switch (len) + { + case 3: case 2: + /* this byte is 4-aligned */ state = __builtin_ia32_crc32hi(state, *(uint16_t*)ptr); - break; + if (len == 2) break; + ptr += 2; + /* fall through */ case 1: state = __builtin_ia32_crc32qi(state, *ptr); + break; + default: + assert(0); } - return state; } static inline gu_crc32c_t crc32c_x86(gu_crc32c_t state, const uint8_t* ptr, size_t len) { + if (0 == len) return state; + static size_t const arg_size = sizeof(uint32_t); - /* apparently no ptr misalignment protection is needed */ + size_t align_offset = ((uintptr_t)ptr) % arg_size; + if (align_offset) + { + align_offset = arg_size - align_offset; + if (align_offset > len) align_offset = len; + state = crc32c_x86_head3(state, ptr, align_offset); + len -= align_offset; + ptr += align_offset; + } + while (len >= arg_size) { state = __builtin_ia32_crc32si(state, *(uint32_t*)ptr); @@ -55,7 +99,7 @@ crc32c_x86(gu_crc32c_t state, const uint assert(len < 4); - return crc32c_x86_tail3(state, ptr, len); + return (len ? crc32c_x86_tail3(state, ptr, len) : state); } gu_crc32c_t @@ -71,7 +115,20 @@ gu_crc32c_x86_64(gu_crc32c_t state, cons const uint8_t* ptr = (const uint8_t*)data; #ifdef __LP64__ + if (0 == len) return state; + static size_t const arg_size = sizeof(uint64_t); + + size_t align_offset = ((uintptr_t)ptr) % arg_size; + if (align_offset) + { + align_offset = arg_size - align_offset; + if (align_offset > len) align_offset = len; + state = crc32c_x86(state, ptr, align_offset); + len -= align_offset; + ptr += align_offset; + } + uint64_t state64 = state; while (len >= arg_size) --- a/galerautils/src/gu_mmh3.c +++ b/galerautils/src/gu_mmh3.c @@ -257,10 +257,11 @@ static uint64_t const GU_MMH128_SEED2 = extern void gu_mmh128 (const void* const msg, size_t const len, void* const out) { - _mmh3_128_seed (msg, len, GU_MMH128_SEED1, GU_MMH128_SEED2, (uint64_t*)out); - uint64_t* const res = (uint64_t*)out; + uint64_t res[2]; + _mmh3_128_seed (msg, len, GU_MMH128_SEED1, GU_MMH128_SEED2, res); res[0] = gu_le64(res[0]); res[1] = gu_le64(res[1]); + memcpy(out, res, sizeof(res)); } /* returns hash as an integer, in host byte-order */ @@ -378,8 +379,9 @@ gu_mmh128_get32 (const gu_mmh128_ctx_t* void gu_mmh3_32 (const void* const key, int const len, uint32_t const seed, void* const out) { - uint32_t const res = _mmh32_seed (key, len, seed); - *((uint32_t*)out) = gu_le32(res); + uint32_t res = _mmh32_seed (key, len, seed); + res = gu_le32(res); + memcpy(out, &res, sizeof(res)); } //----------------------------------------------------------------------------- --- a/galerautils/src/gu_utils.c +++ b/galerautils/src/gu_utils.c @@ -1,4 +1,4 @@ -// Copyright (C) 2010 Codership Oy +// Copyright (C) 2010-2023 Codership Oy /** * @file Miscellaneous utility functions @@ -42,13 +42,20 @@ gu_str2ll (const char* str, long long* l shift += 10; ret++; - if (llret == ((llret << (shift + 1)) >> (shift + 1))) { - llret <<= shift; - } - else { /* ERANGE */ - if (llret > 0) llret = LLONG_MAX; - else llret = LLONG_MIN; - errno = ERANGE; + { + long long const sign = (llret < 0 ? -1 : 1); + unsigned long long ullret = sign * llret; + + if (ullret == ((ullret << (shift + 1)) >> (shift + 1))) { + ullret <<= shift; + llret = ullret; + llret *= sign; + } + else { /* ERANGE */ + if (llret > 0) llret = LLONG_MAX; + else llret = LLONG_MIN; + errno = ERANGE; + } } /* fall through */ default: --- a/galera/src/certification.cpp +++ b/galera/src/certification.cpp @@ -98,8 +98,8 @@ check_purge_complete(const galera::Certi { std::for_each( cert_index.begin(), cert_index.end(), - [&cert_index, &key_set, - ts](const galera::Certification::CertIndexNG::value_type& ke) { + [&key_set, ts] + (const galera::Certification::CertIndexNG::value_type& ke) { ke->for_each_ref([&ke, &key_set, ts](const TrxHandleSlave* ref) { if (ts == ref) { --- a/gcache/src/gcache_mem_store.hpp +++ b/gcache/src/gcache_mem_store.hpp @@ -92,16 +92,19 @@ namespace gcache void* realloc (void* ptr, size_type size) { - BufferHeader* bh(0); - size_type old_size(0); + if (!ptr) return malloc(size); - if (ptr) + BufferHeader* bh(ptr2BH(ptr)); + assert (SEQNO_NONE == bh->seqno_g); + + if (!size) { - bh = ptr2BH(ptr); - assert (SEQNO_NONE == bh->seqno_g); - old_size = bh->size; + free(bh); + return nullptr; } + uintptr_t const orig(reinterpret_cast(bh)); + size_type const old_size(bh->size); diff_type const diff_size(size - old_size); if (size > max_size_ || @@ -109,11 +112,11 @@ namespace gcache assert (size_ + diff_size <= max_size_); + allocd_.erase(bh); void* tmp = ::realloc (bh, size); if (tmp) { - allocd_.erase(bh); allocd_.insert(tmp); bh = BH_cast(tmp); @@ -124,6 +127,13 @@ namespace gcache return (bh + 1); } + else + { + assert(size > 0); + /* orginal buffer is still allocated so we need to restore it + * but we can't use bh directly due to GCC warnings */ + allocd_.insert(reinterpret_cast(orig)); + } return 0; } @@ -133,8 +143,8 @@ namespace gcache assert (BH_is_released(bh)); size_ -= bh->size; - ::free (bh); allocd_.erase(bh); + ::free (bh); } void set_max_size (size_t size) { max_size_ = size; } --- a/gcache/src/gcache_rb_store.cpp +++ b/gcache/src/gcache_rb_store.cpp @@ -798,7 +798,8 @@ namespace gcache sizeof(cs_old))); std::ostringstream msg; - msg << "Attempt to reuse the same seqno: " << seqno_g + msg << "Attempt (" << collision_count + << ") to reuse the same seqno: " << seqno_g << ". New ptr = " << new_ptr << ", " << bh << ", cs: " << gu::Hexdump(cs_new, sizeof(cs_new)) << ", previous ptr = " << old_ptr; @@ -1230,7 +1231,7 @@ namespace gcache size_t chain_count[] = { 0, 0, 0, 0 }; chain_t chain(NONE); - const uint8_t* chain_start; + const uint8_t* chain_start(start_); size_t count; bool next(false); --- a/gcs/src/gcs_group.cpp +++ b/gcs/src/gcs_group.cpp @@ -1409,7 +1409,7 @@ group_for_each_donor_in_string (const gc * that at least one of the nodes in the list will become available. */ if (-EAGAIN != err) err = idx; - begin = end + 1; /* skip comma */ + if (end) begin = end + 1; /* skip comma */ } while (end != NULL); @@ -1498,7 +1498,7 @@ group_find_ist_donor_by_name_in_string ( ret = idx; } } - begin = end + 1; + if (end) begin = end + 1; } while (end != NULL); if (ret == -1) { --- a/gcs/src/unit_tests/gcs_core_test.cpp +++ b/gcs/src/unit_tests/gcs_core_test.cpp @@ -506,7 +506,7 @@ START_TEST (gcs_core_test_api) size_t act_size = sizeof(act3_str); action_t act_s(act, NULL, NULL, act_size, GCS_ACT_WRITESET, -1, (gu_thread_t)-1); - action_t act_r(act, NULL, NULL, -1, (gcs_act_type_t)-1, -1, (gu_thread_t)-1); + action_t act_r(act, NULL, NULL, -1, (gcs_act_type_t)GCS_ACT_UNKNOWN, -1, (gu_thread_t)-1); long i = 5; // test basic fragmentaiton @@ -610,7 +610,7 @@ CORE_TEST_OWN (int gcs_proto_ver) size_t act_size = sizeof(act2_str); action_t act_s(act, NULL, NULL, act_size, GCS_ACT_WRITESET, -1, (gu_thread_t)-1); - action_t act_r(act, NULL, NULL, -1, (gcs_act_type_t)-1, -1, (gu_thread_t)-1); + action_t act_r(act, NULL, NULL, -1, (gcs_act_type_t)GCS_ACT_UNKNOWN, -1, (gu_thread_t)-1); // Create primary and non-primary component messages gcs_comp_msg_t* prim = gcs_comp_msg_new (true, false, 0, 1, 0); --- a/gcomm/src/evs_message2.cpp +++ b/gcomm/src/evs_message2.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 Codership Oy + * Copyright (C) 2009-2023 Codership Oy * * $Id$ */ @@ -179,8 +179,16 @@ size_t gcomm::evs::Message::serialize(gu return offset; } +gcomm::evs::Message::Type gcomm::evs::Message::get_type(const gu::byte_t* buf, + size_t buflen, + size_t offset) +{ + uint8_t b; + gu_trace(offset = gu::unserialize1(buf, buflen, offset, b)); + return static_cast((b >> 2) & 0x7); +} -size_t gcomm::evs::Message::unserialize(const gu::byte_t* const buf, +size_t gcomm::evs::Message::unserialize_common(const gu::byte_t* const buf, size_t const buflen, size_t offset) { @@ -267,13 +275,9 @@ size_t gcomm::evs::UserMessage::serializ size_t gcomm::evs::UserMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); gu_trace(offset = gu::unserialize1(buf, buflen, offset, user_type_)); uint8_t b; gu_trace(offset = gu::unserialize1(buf, buflen, offset, b)); @@ -340,13 +344,9 @@ size_t gcomm::evs::DelegateMessage::seri size_t gcomm::evs::DelegateMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); return offset; } @@ -371,13 +371,9 @@ size_t gcomm::evs::GapMessage::serialize size_t gcomm::evs::GapMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, seq_)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, aru_seq_)); gu_trace(offset = range_uuid_.unserialize(buf, buflen, offset)); @@ -406,13 +402,9 @@ size_t gcomm::evs::JoinMessage::serializ size_t gcomm::evs::JoinMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, seq_)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, aru_seq_)); node_list_.clear(); @@ -441,13 +433,9 @@ size_t gcomm::evs::InstallMessage::seria size_t gcomm::evs::InstallMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, seq_)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, aru_seq_)); gu_trace(offset = install_view_id_.unserialize(buf, buflen, offset)); @@ -477,13 +465,9 @@ size_t gcomm::evs::LeaveMessage::seriali size_t gcomm::evs::LeaveMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, seq_)); gu_trace(offset = gu::unserialize8(buf, buflen, offset, aru_seq_)); return offset; @@ -512,13 +496,9 @@ size_t gcomm::evs::DelayedListMessage::s size_t gcomm::evs::DelayedListMessage::unserialize(const gu::byte_t* const buf, size_t const buflen, - size_t offset, - bool skip_header) + size_t offset) { - if (skip_header == false) - { - gu_trace(offset = Message::unserialize(buf, buflen, offset)); - } + gu_trace(offset = Message::unserialize_common(buf, buflen, offset)); delayed_list_.clear(); uint8_t list_sz(0); gu_trace(offset = gu::unserialize1(buf, buflen, offset, list_sz)); --- a/gcomm/src/evs_message2.hpp +++ b/gcomm/src/evs_message2.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 Codership Oy + * Copyright (C) 2009-2023 Codership Oy */ #ifndef EVS_MESSAGE2_HPP @@ -290,7 +290,17 @@ public: */ gu::datetime::Date tstamp() const { return tstamp_; } - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset); + /* Read type from message buffer. */ + static Type get_type(const gu::byte_t* buf, size_t buflen, size_t offset); + + /* Unserialize common header. */ + size_t unserialize_common(const gu::byte_t* buf, size_t buflen, + size_t offset); + + /* Unserialize message. */ + virtual size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) + = 0; bool operator==(const Message& cmp) const; @@ -444,8 +454,7 @@ public: void set_aru_seq(const seqno_t as) { aru_seq_ = as; } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset) override; size_t serial_size() const; }; @@ -504,8 +513,8 @@ public: fifo_seq) { } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) override; size_t serial_size() const; }; @@ -537,8 +546,8 @@ public: range) { } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) override; size_t serial_size() const; }; @@ -569,8 +578,8 @@ public: node_list) { } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) override; size_t serial_size() const; }; @@ -602,8 +611,8 @@ public: node_list) { } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) override; size_t serial_size() const; }; @@ -631,8 +640,8 @@ public: flags) { } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) override; size_t serial_size() const; }; @@ -662,8 +671,8 @@ public: const DelayedList& delayed_list() const { return delayed_list_; } size_t serialize(gu::byte_t* buf, size_t buflen, size_t offset) const; - size_t unserialize(const gu::byte_t* buf, size_t buflen, size_t offset, - bool skip_header = false); + size_t unserialize(const gu::byte_t* buf, size_t buflen, + size_t offset) override; size_t serial_size() const; bool operator==(const DelayedListMessage& cmp) const { --- a/gcomm/src/evs_proto.cpp +++ b/gcomm/src/evs_proto.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 Codership Oy + * Copyright (C) 2009-2023 Codership Oy */ #include "evs_proto.hpp" @@ -2490,67 +2490,63 @@ void gcomm::evs::Proto::handle_msg(const // Protolay interface //////////////////////////////////////////////////////////////////////// -size_t gcomm::evs::Proto::unserialize_message(const UUID& source, - const Datagram& rb, - Message* msg) +std::pair, size_t> +gcomm::evs::Proto::unserialize_message(const UUID& source, const Datagram& rb) { - size_t offset; + size_t offset = 0; const gu::byte_t* begin(gcomm::begin(rb)); const size_t available(gcomm::available(rb)); - gu_trace(offset = msg->unserialize(begin, - available, - 0)); - if ((msg->flags() & Message::F_SOURCE) == 0) + std::unique_ptr ret; + switch (Message::get_type(begin, available, offset)) { - assert(source != UUID::nil()); - gcomm_assert(source != UUID::nil()); - msg->set_source(source); - } - - switch (msg->type()) - { - case Message::EVS_T_NONE: - gu_throw_fatal; - break; + case Message::EVS_T_NONE: gu_throw_fatal; break; case Message::EVS_T_USER: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new UserMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; case Message::EVS_T_DELEGATE: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new DelegateMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; case Message::EVS_T_GAP: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new GapMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; case Message::EVS_T_JOIN: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new JoinMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; case Message::EVS_T_INSTALL: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new InstallMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; case Message::EVS_T_LEAVE: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new LeaveMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; case Message::EVS_T_DELAYED_LIST: - gu_trace(offset = static_cast(*msg).unserialize( - begin, available, offset, true)); + ret = std::unique_ptr(new DelayedListMessage); + gu_trace(offset = ret->unserialize(begin, available, offset)); break; + default: + return {std::unique_ptr{}, 0}; } - return (offset + rb.offset()); + + /* Message did not have source field, must be set from source reported + by the lower layer. */ + if ((ret->flags() & Message::F_SOURCE) == 0) + { + assert(source != UUID::nil()); + gcomm_assert(source != UUID::nil()); + ret->set_source(source); + } + + return {std::move(ret), offset + rb.offset()}; } -void gcomm::evs::Proto::handle_up(const void* cid, - const Datagram& rb, +void gcomm::evs::Proto::handle_up(const void* cid, const Datagram& rb, const ProtoUpMeta& um) { - - Message msg; - if (state() == S_CLOSED || um.source() == uuid() || is_evicted(um.source())) { // Silent drop @@ -2559,12 +2555,16 @@ void gcomm::evs::Proto::handle_up(const gcomm_assert(um.source() != UUID::nil()); + std::pair, size_t> msg; try { - size_t offset; - gu_trace(offset = unserialize_message(um.source(), rb, &msg)); - handle_msg(msg, Datagram(rb, offset), - (msg.flags() & Message::F_RETRANS) == 0); + gu_trace(msg = unserialize_message(um.source(), rb)); + if (not msg.first) { + /* Message could not be serialized. */ + return; + } + handle_msg(*msg.first, Datagram(rb, msg.second), + (msg.first->flags() & Message::F_RETRANS) == 0); } catch (gu::Exception& e) { @@ -2575,11 +2575,11 @@ void gcomm::evs::Proto::handle_up(const break; case EINVAL: - log_warn << "invalid message: " << msg; + log_warn << "invalid message: " << *msg.first; break; default: - log_fatal << "exception caused by message: " << msg; + log_fatal << "exception caused by message: " << *msg.first; std::cerr << " state after handling message: " << *this; throw; } @@ -3791,20 +3791,20 @@ void gcomm::evs::Proto::handle_user(cons } } - void gcomm::evs::Proto::handle_delegate(const DelegateMessage& msg, NodeMap::iterator ii, const Datagram& rb) { gcomm_assert(ii != known_.end()); evs_log_debug(D_DELEGATE_MSGS) << "delegate message " << msg; - Message umsg; - size_t offset; - gu_trace(offset = unserialize_message(UUID::nil(), rb, &umsg)); - gu_trace(handle_msg(umsg, Datagram(rb, offset), false)); + std::pair, size_t> umsg; + gu_trace(umsg = unserialize_message(UUID::nil(), rb)); + if (not umsg.first) { + return; + } + gu_trace(handle_msg(*umsg.first, Datagram(rb, umsg.second), false)); } - void gcomm::evs::Proto::handle_gap(const GapMessage& msg, NodeMap::iterator ii) { assert(ii != known_.end()); --- a/gcomm/src/evs_proto.hpp +++ b/gcomm/src/evs_proto.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 Codership Oy + * Copyright (C) 2009-2023 Codership Oy */ /*! @@ -288,11 +288,9 @@ private: void populate_node_list(MessageNodeList*) const; void isolate(gu::datetime::Period period); public: - static size_t unserialize_message(const UUID&, - const Datagram&, - Message*); - void handle_msg(const Message& msg, - const Datagram& dg = Datagram(), + static std::pair, size_t> + unserialize_message(const UUID&, const Datagram&); + void handle_msg(const Message& msg, const Datagram& dg = Datagram(), bool direct = true); // Protolay void handle_up(const void*, const Datagram&, const ProtoUpMeta&); --- a/gcomm/test/check_evs2.cpp +++ b/gcomm/test/check_evs2.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2020 Codership Oy + * Copyright (C) 2009-2023 Codership Oy */ /*! @@ -455,12 +455,13 @@ START_TEST(test_input_map_gap_range_list } END_TEST -static Datagram* get_msg(DummyTransport* tp, Message* msg, bool release = true) +static Datagram* get_msg(DummyTransport* tp, std::unique_ptr& msg, + bool release = true) { Datagram* rb = tp->out(); if (rb != 0) { - gu_trace(Proto::unserialize_message(tp->uuid(), *rb, msg)); + msg = Proto::unserialize_message(tp->uuid(), *rb).first; if (release == true) { delete rb; @@ -471,7 +472,7 @@ static Datagram* get_msg(DummyTransport* static void single_join(DummyTransport* t, Proto* p) { - Message jm, im, gm; + std::unique_ptr jm, im, gm; // Initial state is joining p->shift_to(Proto::S_JOINING); @@ -479,42 +480,42 @@ static void single_join(DummyTransport* // Send join must produce emitted join message p->send_join(); - Datagram* rb = get_msg(t, &jm); + Datagram* rb = get_msg(t, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); + ck_assert(jm->type() == Message::EVS_T_JOIN); // Install message is emitted at the end of JOIN handling // 'cause this is the only instance and is always consistent // with itself - rb = get_msg(t, &im); + rb = get_msg(t, im); ck_assert(rb != 0); - ck_assert(im.type() == Message::EVS_T_INSTALL); + ck_assert(im->type() == Message::EVS_T_INSTALL); // Handling INSTALL message emits three gap messages, // one for receiving install message (commit gap), one for // shift to install and one for shift to operational - rb = get_msg(t, &gm); + rb = get_msg(t, gm); ck_assert(rb != 0); - ck_assert(gm.type() == Message::EVS_T_GAP); - ck_assert((gm.flags() & Message::F_COMMIT) != 0); + ck_assert(gm->type() == Message::EVS_T_GAP); + ck_assert((gm->flags() & Message::F_COMMIT) != 0); - rb = get_msg(t, &gm); + rb = get_msg(t, gm); ck_assert(rb != 0); - ck_assert(gm.type() == Message::EVS_T_GAP); - ck_assert((gm.flags() & Message::F_COMMIT) == 0); + ck_assert(gm->type() == Message::EVS_T_GAP); + ck_assert((gm->flags() & Message::F_COMMIT) == 0); - rb = get_msg(t, &gm); + rb = get_msg(t, gm); ck_assert(rb != 0); - ck_assert(gm.type() == Message::EVS_T_GAP); - ck_assert((gm.flags() & Message::F_COMMIT) == 0); + ck_assert(gm->type() == Message::EVS_T_GAP); + ck_assert((gm->flags() & Message::F_COMMIT) == 0); // State must have evolved JOIN -> S_GATHER -> S_INSTALL -> S_OPERATIONAL ck_assert(p->state() == Proto::S_OPERATIONAL); // Handle join message again, must stay in S_OPERATIONAL, must not // emit anything - p->handle_msg(jm); - rb = get_msg(t, &gm); + p->handle_msg(*jm); + rb = get_msg(t, gm); ck_assert(rb == 0); ck_assert(p->state() == Proto::S_OPERATIONAL); @@ -553,11 +554,11 @@ static void double_join(DummyTransport* DummyTransport* t2, Proto* p2) { - Message jm; - Message im; - Message gm; - Message gm2; - Message msg; + std::unique_ptr jm; + std::unique_ptr im; + std::unique_ptr gm; + std::unique_ptr gm2; + std::unique_ptr msg; Datagram* rb; @@ -570,99 +571,99 @@ static void double_join(DummyTransport* // Expected output: one join message p2->send_join(false); ck_assert(p2->state() == Proto::S_JOINING); - rb = get_msg(t2, &jm); + rb = get_msg(t2, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(t2, &msg); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(t2, msg); ck_assert(rb == 0); // Handle node 2's join on node 1 // Expected output: shift to S_GATHER and one join message - p1->handle_msg(jm); + p1->handle_msg(*jm); ck_assert(p1->state() == Proto::S_GATHER); - rb = get_msg(t1, &jm); + rb = get_msg(t1, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(t1, &msg); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(t1, msg); ck_assert(rb == 0); // Handle node 1's join on node 2 // Expected output: shift to S_GATHER and one join message - p2->handle_msg(jm); + p2->handle_msg(*jm); ck_assert(p2->state() == Proto::S_GATHER); - rb = get_msg(t2, &jm); + rb = get_msg(t2, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(t2, &msg); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(t2, msg); ck_assert(rb == 0); // Handle node 2's join on node 1 // Expected output: Install and commit gap messages, state stays in S_GATHER - p1->handle_msg(jm); + p1->handle_msg(*jm); ck_assert(p1->state() == Proto::S_GATHER); - rb = get_msg(t1, &im); + rb = get_msg(t1, im); ck_assert(rb != 0); - ck_assert(im.type() == Message::EVS_T_INSTALL); - rb = get_msg(t1, &gm); + ck_assert(im->type() == Message::EVS_T_INSTALL); + rb = get_msg(t1, gm); ck_assert(rb != 0); - ck_assert(gm.type() == Message::EVS_T_GAP); - ck_assert((gm.flags() & Message::F_COMMIT) != 0); - rb = get_msg(t1, &msg); + ck_assert(gm->type() == Message::EVS_T_GAP); + ck_assert((gm->flags() & Message::F_COMMIT) != 0); + rb = get_msg(t1, msg); ck_assert(rb == 0); // Handle install message on node 2 // Expected output: commit gap message and state stays in S_RECOVERY - p2->handle_msg(im); + p2->handle_msg(*im); ck_assert(p2->state() == Proto::S_GATHER); - rb = get_msg(t2, &gm2); + rb = get_msg(t2, gm2); ck_assert(rb != 0); - ck_assert(gm2.type() == Message::EVS_T_GAP); - ck_assert((gm2.flags() & Message::F_COMMIT) != 0); - rb = get_msg(t2, &msg); + ck_assert(gm2->type() == Message::EVS_T_GAP); + ck_assert((gm2->flags() & Message::F_COMMIT) != 0); + rb = get_msg(t2, msg); ck_assert(rb == 0); // Handle gap messages // Expected output: Both nodes shift to S_INSTALL, // both send gap messages - p1->handle_msg(gm2); + p1->handle_msg(*gm2); ck_assert(p1->state() == Proto::S_INSTALL); - Message gm12; - rb = get_msg(t1, &gm12); + std::unique_ptr gm12; + rb = get_msg(t1, gm12); ck_assert(rb != 0); - ck_assert(gm12.type() == Message::EVS_T_GAP); - ck_assert((gm12.flags() & Message::F_COMMIT) == 0); - rb = get_msg(t1, &msg); + ck_assert(gm12->type() == Message::EVS_T_GAP); + ck_assert((gm12->flags() & Message::F_COMMIT) == 0); + rb = get_msg(t1, msg); ck_assert(rb == 0); - p2->handle_msg(gm); + p2->handle_msg(*gm); ck_assert(p2->state() == Proto::S_INSTALL); - Message gm22; - rb = get_msg(t2, &gm22); + std::unique_ptr gm22; + rb = get_msg(t2, gm22); ck_assert(rb != 0); - ck_assert(gm22.type() == Message::EVS_T_GAP); - ck_assert((gm22.flags() & Message::F_COMMIT) == 0); - rb = get_msg(t2, &msg); + ck_assert(gm22->type() == Message::EVS_T_GAP); + ck_assert((gm22->flags() & Message::F_COMMIT) == 0); + rb = get_msg(t2, msg); ck_assert(rb == 0); // Handle final gap messages, expected output shift to operational // and gap message - p1->handle_msg(gm22); + p1->handle_msg(*gm22); ck_assert(p1->state() == Proto::S_OPERATIONAL); - rb = get_msg(t1, &msg); + rb = get_msg(t1, msg); ck_assert(rb != 0); - ck_assert(msg.type() == Message::EVS_T_GAP); - ck_assert((msg.flags() & Message::F_COMMIT) == 0); - rb = get_msg(t1, &msg); + ck_assert(msg->type() == Message::EVS_T_GAP); + ck_assert((msg->flags() & Message::F_COMMIT) == 0); + rb = get_msg(t1, msg); ck_assert(rb == 0); - p2->handle_msg(gm12); + p2->handle_msg(*gm12); ck_assert(p2->state() == Proto::S_OPERATIONAL); - rb = get_msg(t2, &msg); + rb = get_msg(t2, msg); ck_assert(rb != 0); - ck_assert(msg.type() == Message::EVS_T_GAP); - ck_assert((msg.flags() & Message::F_COMMIT) == 0); - rb = get_msg(t2, &msg); + ck_assert(msg->type() == Message::EVS_T_GAP); + ck_assert((msg->flags() & Message::F_COMMIT) == 0); + rb = get_msg(t2, msg); ck_assert(rb == 0); } @@ -2031,12 +2032,12 @@ START_TEST(test_gh_100) // install message is generated. After that handle install timer // on p1 and verify that the newly generated install message has // greater install view id seqno than the first one. - Message jm; - Message im; - Message im2; - Message gm; - Message gm2; - Message msg; + std::unique_ptr jm; + std::unique_ptr im; + std::unique_ptr im2; + std::unique_ptr gm; + std::unique_ptr gm2; + std::unique_ptr msg; Datagram* rb; @@ -2049,56 +2050,56 @@ START_TEST(test_gh_100) // Expected output: one join message p2.send_join(false); ck_assert(p2.state() == Proto::S_JOINING); - rb = get_msg(&t2, &jm); + rb = get_msg(&t2, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(&t2, &msg); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(&t2, msg); ck_assert(rb == 0); // Handle node 2's join on node 1 // Expected output: shift to S_GATHER and one join message - p1.handle_msg(jm); + p1.handle_msg(*jm); ck_assert(p1.state() == Proto::S_GATHER); - rb = get_msg(&t1, &jm); + rb = get_msg(&t1, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(&t1, &msg); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(&t1, msg); ck_assert(rb == 0); // Handle node 1's join on node 2 // Expected output: shift to S_GATHER and one join message - p2.handle_msg(jm); + p2.handle_msg(*jm); ck_assert(p2.state() == Proto::S_GATHER); - rb = get_msg(&t2, &jm); + rb = get_msg(&t2, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(&t2, &msg); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(&t2, msg); ck_assert(rb == 0); // Handle node 2's join on node 1 // Expected output: Install and commit gap messages, state stays in S_GATHER - p1.handle_msg(jm); + p1.handle_msg(*jm); ck_assert(p1.state() == Proto::S_GATHER); - rb = get_msg(&t1, &im); + rb = get_msg(&t1, im); ck_assert(rb != 0); - ck_assert(im.type() == Message::EVS_T_INSTALL); - rb = get_msg(&t1, &gm); + ck_assert(im->type() == Message::EVS_T_INSTALL); + rb = get_msg(&t1, gm); ck_assert(rb != 0); - ck_assert(gm.type() == Message::EVS_T_GAP); - ck_assert((gm.flags() & Message::F_COMMIT) != 0); - rb = get_msg(&t1, &msg); + ck_assert(gm->type() == Message::EVS_T_GAP); + ck_assert((gm->flags() & Message::F_COMMIT) != 0); + rb = get_msg(&t1, msg); ck_assert(rb == 0); // Handle timers to to generate shift to GATHER p1.handle_inactivity_timer(); p1.handle_install_timer(); - rb = get_msg(&t1, &jm); + rb = get_msg(&t1, jm); ck_assert(rb != 0); - ck_assert(jm.type() == Message::EVS_T_JOIN); - rb = get_msg(&t1, &im2); + ck_assert(jm->type() == Message::EVS_T_JOIN); + rb = get_msg(&t1, im2); ck_assert(rb != 0); - ck_assert(im2.type() == Message::EVS_T_INSTALL); - ck_assert(im2.install_view_id().seq() > im.install_view_id().seq()); + ck_assert(im2->type() == Message::EVS_T_INSTALL); + ck_assert(im2->install_view_id().seq() > im->install_view_id().seq()); gcomm::Datagram* tmp; while ((tmp = t1.out())) delete tmp; @@ -2195,14 +2196,14 @@ START_TEST(test_gal_521) ck_assert(t2->empty() == false); Datagram *d1; - Message um1; - ck_assert((d1 = get_msg(t1, &um1, false)) != 0); - ck_assert(um1.type() == Message::EVS_T_USER); + std::unique_ptr um1; + ck_assert((d1 = get_msg(t1, um1, false)) != 0); + ck_assert(um1->type() == Message::EVS_T_USER); ck_assert(t1->empty() == true); Datagram *d2; - Message um2; - ck_assert((d2 = get_msg(t2, &um2, false)) != 0); - ck_assert(um2.type() == Message::EVS_T_USER); + std::unique_ptr um2; + ck_assert((d2 = get_msg(t2, um2, false)) != 0); + ck_assert(um2->type() == Message::EVS_T_USER); ck_assert(t2->empty() == true); // Both of the nodes handle each other's messages. Now due to @@ -2211,53 +2212,53 @@ START_TEST(test_gal_521) // must emit gap messages to make safe_seq to progress. evs1->handle_up(0, *d2, ProtoUpMeta(dn[1]->uuid())); delete d2; - Message gm1; - ck_assert(get_msg(t1, &gm1) != 0); - ck_assert(gm1.type() == Message::EVS_T_GAP); + std::unique_ptr gm1; + ck_assert(get_msg(t1, gm1) != 0); + ck_assert(gm1->type() == Message::EVS_T_GAP); ck_assert(t1->empty() == true); evs2->handle_up(0, *d1, ProtoUpMeta(dn[0]->uuid())); delete d1; - Message gm2; - ck_assert(get_msg(t2, &gm2) != 0); - ck_assert(gm2.type() == Message::EVS_T_GAP); + std::unique_ptr gm2; + ck_assert(get_msg(t2, gm2) != 0); + ck_assert(gm2->type() == Message::EVS_T_GAP); ck_assert(t2->empty() == true); // Handle gap messages. The safe_seq is now incremented so the // second user messages are now sent from output queue. - evs1->handle_msg(gm2); - ck_assert((d1 = get_msg(t1, &um1, false)) != 0); - ck_assert(um1.type() == Message::EVS_T_USER); + evs1->handle_msg(*gm2); + ck_assert((d1 = get_msg(t1, um1, false)) != 0); + ck_assert(um1->type() == Message::EVS_T_USER); ck_assert(t1->empty() == true); - evs2->handle_msg(gm1); - ck_assert((d2 = get_msg(t2, &um2, false)) != 0); - ck_assert(um2.type() == Message::EVS_T_USER); + evs2->handle_msg(*gm1); + ck_assert((d2 = get_msg(t2, um2, false)) != 0); + ck_assert(um2->type() == Message::EVS_T_USER); ck_assert(t2->empty() == true); // Handle user messages. Each node should now emit gap // because the output queue is empty. evs1->handle_up(0, *d2, ProtoUpMeta(dn[1]->uuid())); delete d2; - ck_assert(get_msg(t1, &gm1) != 0); - ck_assert(gm1.type() == Message::EVS_T_GAP); + ck_assert(get_msg(t1, gm1) != 0); + ck_assert(gm1->type() == Message::EVS_T_GAP); ck_assert(t1->empty() == true); evs2->handle_up(0, *d1, ProtoUpMeta(dn[0]->uuid())); delete d1; - ck_assert(get_msg(t2, &gm2) != 0); - ck_assert(gm2.type() == Message::EVS_T_GAP); + ck_assert(get_msg(t2, gm2) != 0); + ck_assert(gm2->type() == Message::EVS_T_GAP); ck_assert(t2->empty() == true); // Handle gap messages. No further messages should be emitted // since both user messages have been delivered, there are // no pending user messages in the output queue and no timers // have been expired. - evs1->handle_msg(gm2); - ck_assert((d1 = get_msg(t1, &um1, false)) == 0); + evs1->handle_msg(*gm2); + ck_assert((d1 = get_msg(t1, um1, false)) == 0); - evs2->handle_msg(gm1); - ck_assert((d2 = get_msg(t2, &um2, false)) == 0); + evs2->handle_msg(*gm1); + ck_assert((d2 = get_msg(t2, um2, false)) == 0); std::for_each(dn.begin(), dn.end(), DeleteObject()); @@ -2332,70 +2333,70 @@ START_TEST(test_gap_rate_limit) // the rest are handled by node2 for generating gap messages. f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); gcomm::Datagram* read_dg; - gcomm::evs::Message um1; - read_dg = get_msg(&f.tr1, &um1); + std::unique_ptr um1; + read_dg = get_msg(&f.tr1, um1); ck_assert(read_dg != 0); f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); - gcomm::evs::Message um2; - read_dg = get_msg(&f.tr1, &um2); + std::unique_ptr um2; + read_dg = get_msg(&f.tr1, um2); ck_assert(read_dg != 0); f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); - gcomm::evs::Message um3; - read_dg = get_msg(&f.tr1, &um3); + std::unique_ptr um3; + read_dg = get_msg(&f.tr1, um3); ck_assert(read_dg != 0); f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); - gcomm::evs::Message um4; - read_dg = get_msg(&f.tr1, &um4); + std::unique_ptr um4; + read_dg = get_msg(&f.tr1, um4); ck_assert(read_dg != 0); // Make node2 handle an out of order message and verify that gap is emitted - f.evs2.handle_msg(um2); - gcomm::evs::Message gm1; - read_dg = get_msg(&f.tr2, &gm1); + f.evs2.handle_msg(*um2); + std::unique_ptr gm1; + read_dg = get_msg(&f.tr2, gm1); ck_assert(read_dg != 0); - ck_assert(gm1.type() == gcomm::evs::Message::EVS_T_GAP); - ck_assert(gm1.range_uuid() == f.uuid1); - ck_assert(gm1.range().lu() == 0); - ck_assert(gm1.range().hs() == 0); + ck_assert(gm1->type() == gcomm::evs::Message::EVS_T_GAP); + ck_assert(gm1->range_uuid() == f.uuid1); + ck_assert(gm1->range().lu() == 0); + ck_assert(gm1->range().hs() == 0); // The node2 will also send an user message to complete the sequence // number. Consume it. - gcomm::evs::Message comp_um1; - read_dg = get_msg(&f.tr2, &comp_um1); + std::unique_ptr comp_um1; + read_dg = get_msg(&f.tr2, comp_um1); ck_assert(read_dg != 0); - ck_assert(comp_um1.type() == gcomm::evs::Message::EVS_T_USER); - ck_assert(comp_um1.seq() + comp_um1.seq_range() == 1); + ck_assert(comp_um1->type() == gcomm::evs::Message::EVS_T_USER); + ck_assert(comp_um1->seq() + comp_um1->seq_range() == 1); // No further messages should be emitted - read_dg = get_msg(&f.tr2, &comp_um1); + read_dg = get_msg(&f.tr2, comp_um1); ck_assert(read_dg == 0); // Handle the second out of order message, gap should not be emitted. // There will be a next user message which completes the um3. - f.evs2.handle_msg(um3); - gcomm::evs::Message comp_um2; - read_dg = get_msg(&f.tr2, &comp_um2); + f.evs2.handle_msg(*um3); + std::unique_ptr comp_um2; + read_dg = get_msg(&f.tr2, comp_um2); ck_assert(read_dg != 0); - ck_assert(comp_um2.type() == gcomm::evs::Message::EVS_T_USER); - ck_assert(comp_um2.seq() + comp_um2.seq_range() == 2); + ck_assert(comp_um2->type() == gcomm::evs::Message::EVS_T_USER); + ck_assert(comp_um2->seq() + comp_um2->seq_range() == 2); // There should not be any more gap messages. - read_dg = get_msg(&f.tr2, &gm1); + read_dg = get_msg(&f.tr2, gm1); ck_assert(read_dg == 0); // Move the clock forwards and handle the fourth message, gap should // now emitted. gu::datetime::SimClock::inc_time(100*gu::datetime::MSec); - gcomm::evs::Message gm2; - f.evs2.handle_msg(um4); - read_dg = get_msg(&f.tr2, &gm2); + std::unique_ptr gm2; + f.evs2.handle_msg(*um4); + read_dg = get_msg(&f.tr2, gm2); ck_assert(read_dg != 0); - ck_assert(gm2.type() == gcomm::evs::Message::EVS_T_GAP); - ck_assert(gm2.range().lu() == 0); - ck_assert(gm2.range().hs() == 0); + ck_assert(gm2->type() == gcomm::evs::Message::EVS_T_GAP); + ck_assert(gm2->range().lu() == 0); + ck_assert(gm2->range().hs() == 0); - gcomm::evs::Message comp_u4; - read_dg = get_msg(&f.tr2, &comp_u4); + std::unique_ptr comp_u4; + read_dg = get_msg(&f.tr2, comp_u4); ck_assert(read_dg != 0); - ck_assert(comp_u4.type() == gcomm::evs::Message::EVS_T_USER); + ck_assert(comp_u4->type() == gcomm::evs::Message::EVS_T_USER); log_info << "END test_gap_rate_limit"; } END_TEST @@ -2427,75 +2428,75 @@ START_TEST(test_gap_rate_limit_delayed) // the rest are handled by node2 for generating gap messages. f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); gcomm::Datagram* read_dg; - gcomm::evs::Message um1; - read_dg = get_msg(&f.tr1, &um1); + std::unique_ptr um1; + read_dg = get_msg(&f.tr1, um1); ck_assert(read_dg != 0); f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); - gcomm::evs::Message um2; - read_dg = get_msg(&f.tr1, &um2); + std::unique_ptr um2; + read_dg = get_msg(&f.tr1, um2); ck_assert(read_dg != 0); f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); - gcomm::evs::Message um3; - read_dg = get_msg(&f.tr1, &um3); + std::unique_ptr um3; + read_dg = get_msg(&f.tr1, um3); ck_assert(read_dg != 0); f.evs1.handle_down(dg, ProtoDownMeta(O_SAFE)); - gcomm::evs::Message um4; - read_dg = get_msg(&f.tr1, &um4); + std::unique_ptr um4; + read_dg = get_msg(&f.tr1, um4); ck_assert(read_dg != 0); // Make node2 handle an out of order message and verify that gap is emitted - f.evs2.handle_msg(um2); - gcomm::evs::Message gm1; - read_dg = get_msg(&f.tr2, &gm1); + f.evs2.handle_msg(*um2); + std::unique_ptr gm1; + read_dg = get_msg(&f.tr2, gm1); ck_assert(read_dg != 0); - ck_assert(gm1.type() == gcomm::evs::Message::EVS_T_GAP); - ck_assert(gm1.range_uuid() == f.uuid1); - ck_assert(gm1.range().lu() == 0); - ck_assert(gm1.range().hs() == 0); + ck_assert(gm1->type() == gcomm::evs::Message::EVS_T_GAP); + ck_assert(gm1->range_uuid() == f.uuid1); + ck_assert(gm1->range().lu() == 0); + ck_assert(gm1->range().hs() == 0); // The node2 will also send an user message to complete the sequence // number. Consume it. - gcomm::evs::Message comp_um1; - read_dg = get_msg(&f.tr2, &comp_um1); + std::unique_ptr comp_um1; + read_dg = get_msg(&f.tr2, comp_um1); ck_assert(read_dg != 0); - ck_assert(comp_um1.type() == gcomm::evs::Message::EVS_T_USER); - ck_assert(comp_um1.seq() + comp_um1.seq_range() == 1); + ck_assert(comp_um1->type() == gcomm::evs::Message::EVS_T_USER); + ck_assert(comp_um1->seq() + comp_um1->seq_range() == 1); // No further messages should be emitted - read_dg = get_msg(&f.tr2, &comp_um1); + read_dg = get_msg(&f.tr2, comp_um1); ck_assert(read_dg == 0); // Move time forwards in 1 sec interval and make inactivity check // in between. No gap messages should be emitted. gu::datetime::SimClock::inc_time(gu::datetime::Sec); f.evs2.handle_inactivity_timer(); - gcomm::evs::Message gm_discard; - read_dg = get_msg(&f.tr2, &gm_discard); + std::unique_ptr gm_discard; + read_dg = get_msg(&f.tr2, gm_discard); ck_assert(read_dg == 0); // The clock is now advanced over retrans_period + delay margin. Next // call to handle_inactivity_timer() should fire the check. Gap message // is emitted. gu::datetime::SimClock::inc_time(gu::datetime::Sec); f.evs2.handle_inactivity_timer(); - read_dg = get_msg(&f.tr2, &gm1); + read_dg = get_msg(&f.tr2, gm1); ck_assert(read_dg != 0); - ck_assert(gm1.type() == gcomm::evs::Message::EVS_T_GAP); + ck_assert(gm1->type() == gcomm::evs::Message::EVS_T_GAP); // Now call handle_inactivity_timer() again, gap message should not // be emitted due to rate limit. // Galera 4 will run with evs protocol version 1 and will emit // delayed list at this point. f.evs2.handle_inactivity_timer(); - gcomm::evs::Message dm; - read_dg = get_msg(&f.tr2, &dm); + std::unique_ptr dm; + read_dg = get_msg(&f.tr2, dm); ck_assert(read_dg != 0); - ck_assert(dm.type() == gcomm::evs::Message::EVS_T_DELAYED_LIST); - read_dg = get_msg(&f.tr2, &gm_discard); + ck_assert(dm->type() == gcomm::evs::Message::EVS_T_DELAYED_LIST); + read_dg = get_msg(&f.tr2, gm_discard); ck_assert(read_dg == 0); // Move clock forward 100msec, new gap should be now emitted. gu::datetime::SimClock::inc_time(100*gu::datetime::MSec); f.evs2.handle_inactivity_timer(); - gcomm::evs::Message gm2; - read_dg = get_msg(&f.tr2, &gm2); + std::unique_ptr gm2; + read_dg = get_msg(&f.tr2, gm2); ck_assert(read_dg != 0); - ck_assert(gm2.type() == gcomm::evs::Message::EVS_T_GAP); + ck_assert(gm2->type() == gcomm::evs::Message::EVS_T_GAP); log_info << "END test_gap_rate_limit_delayed"; gcomm::Datagram* tmp; --- a/gcomm/test/check_trace.hpp +++ b/gcomm/test/check_trace.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 Codership Oy + * Copyright (C) 2009-2023 Codership Oy * * $Id$ */ @@ -173,7 +173,7 @@ namespace gcomm std::deque out_; bool queue_; static std::unique_ptr net_; - Protonet& get_net(); + static Protonet& get_net(); public: DummyTransport(const UUID& uuid = UUID::nil(), bool queue = true, --- a/galerautils/tests/CMakeLists.txt +++ b/galerautils/tests/CMakeLists.txt @@ -47,7 +47,6 @@ add_test( # # C++ galerautils tests. # - add_executable(gu_tests++ gu_atomic_test.cpp gu_gtid_test.cpp @@ -75,7 +74,7 @@ add_executable(gu_tests++ target_compile_definitions(gu_tests++ PRIVATE - -DGU_ASIO_TEST_CERT_DIR="${PROJECT_SOURCE_DIR}/tests/conf") + -DGU_ASIO_TEST_CERT_DIR="${CMAKE_CURRENT_BINARY_DIR}/certs") # TODO: These should be eventually fixed. target_compile_options(gu_tests++ @@ -93,7 +92,6 @@ add_test( NAME gu_tests++ COMMAND gu_tests++ ) - # # Deqmap micro benchmark. # --- a/galerautils/tests/gu_asio_test.cpp +++ b/galerautils/tests/gu_asio_test.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2020 Codership Oy + * Copyright (C) 2019-2023 Codership Oy */ @@ -920,18 +920,276 @@ END_TEST #ifdef GALERA_HAVE_SSL +#include +#include +#include +#include +#include +#include +#include +#include #include -// -// SSL -// +#include static std::string get_cert_dir() { - // This will be set by CMake/preprocessor. + static_assert(::strlen(GU_ASIO_TEST_CERT_DIR) > 0); + const std::string ret{ GU_ASIO_TEST_CERT_DIR }; + auto* dir = opendir(ret.c_str()); + if (!dir) + { + if (mkdir(ret.c_str(), S_IRWXU)) + { + const auto* errstr = ::strerror(errno); + gu_throw_fatal << "Could not create dir " << ret << ": " << errstr; + } + } + else + { + closedir(dir); + } return GU_ASIO_TEST_CERT_DIR; } +static int password_cb(char*, int, int, void*) { return 0; } + +static void throw_error(const char* msg) +{ + gu_throw_fatal << msg << ": " << ERR_error_string(ERR_get_error(), nullptr); +} + +static EVP_PKEY* create_key() +{ +#if OPENSSL_VERSION_MAJOR < 3 + auto* bn = BN_new(); + if (!bn) + { + throw_error("could not create BN"); + } + BN_set_word(bn, 0x10001); + auto* rsa = RSA_new(); + if (!rsa) + { + BN_free(bn); + throw_error("could not create RSA"); + } + RSA_generate_key_ex(rsa, 2048, bn, nullptr); + auto* pkey = EVP_PKEY_new(); + if (!pkey) + { + BN_free(bn); + RSA_free(rsa); + throw_error("could not create PKEY"); + } + EVP_PKEY_set1_RSA(pkey, rsa); + RSA_free(rsa); + BN_free(bn); + return pkey; +#else + auto* ret = EVP_RSA_gen(2048); + if (!ret) + { + throw_error("could not create RSA"); + } + return ret; +#endif /* OPENSSL_VERSION_MAJOR < 3 */ +} + +static FILE* open_file(const std::string& path, const char* mode) +{ + auto* ret = fopen(path.c_str(), mode); + if (!ret) + { + const auto* errstr = ::strerror(errno); + gu_throw_fatal << "Could not open file " << path << ": " + << errstr; + } + return ret; +} + +static void write_key(EVP_PKEY* pkey, const std::string& filename) +{ + const std::string cert_dir = get_cert_dir(); + const std::string key_file_path = cert_dir + "/" + filename; + auto* key_file = open_file(key_file_path, "wb"); + if (!PEM_write_PrivateKey(key_file, pkey, nullptr, nullptr, 0, password_cb, + nullptr)) + { + throw_error("Could not write key"); + } + fclose(key_file); +} + +static void set_x509v3_extensions(X509* x509, X509* issuer) +{ + auto* conf_bio = BIO_new(BIO_s_mem()); + std::string ext{ "[extensions]\n" + "authorityKeyIdentifier=keyid,issuer\n" + "subjectKeyIdentifier=hash\n" }; + if (!issuer) + { + ext += "basicConstraints=critical,CA:TRUE\n"; + } + else + { + ext += "keyUsage=digitalSignature,keyEncipherment\n"; + ext += "basicConstraints=CA:FALSE\n"; + } + BIO_printf(conf_bio, "%s", ext.c_str()); + auto* conf = NCONF_new(nullptr); + long errorline = -1; + int err; + if ((err = NCONF_load_bio(conf, conf_bio, &errorline)) <= 0) + { + gu_throw_fatal << "Could not load conf: " << err; + } + if (errorline != -1) + { + gu_throw_fatal << "Could not load conf, errorline: " << errorline; + } + // TODO: V3 extensions + X509V3_CTX ctx; + X509V3_set_ctx(&ctx, issuer ? issuer : x509, x509, nullptr, nullptr, 0); + X509V3_set_nconf(&ctx, conf); + if (!X509V3_EXT_add_nconf(conf, &ctx, "extensions", x509)) + { + throw_error("Could not add extension"); + } + NCONF_free(conf); + BIO_free(conf_bio); +} + +static X509* create_x509(EVP_PKEY* pkey, X509* issuer, const char* cn) +{ + auto* x509 = X509_new(); + /* According to standard, value 2 means version 3. */ + X509_set_version(x509, 2); + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); + X509_set_pubkey(x509, pkey); + + auto* name = X509_get_subject_name(x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, reinterpret_cast("FI"), + -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, + reinterpret_cast("Uusimaa"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, + reinterpret_cast("Helsinki"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, + reinterpret_cast("Codership"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, + reinterpret_cast("Galera Devel"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, reinterpret_cast(cn), -1, + -1, 0); + if (!issuer) + { + /* Self signed */ + X509_set_issuer_name(x509, name); + } + else + { + X509_set_issuer_name(x509, X509_get_subject_name(issuer)); + } + + set_x509v3_extensions(x509, issuer); + + X509_sign(x509, pkey, EVP_sha256()); + + return x509; +} + +static void write_x509(X509* x509, const std::string& filename) +{ + const std::string cert_dir = get_cert_dir(); + const std::string file_path = cert_dir + "/" + filename; + auto* file = open_file(file_path, "wb"); + if (!PEM_write_X509(file, x509)) + { + throw_error("Could not write x509"); + } + fclose(file); +} + +static void write_x509_list(const std::vector& certs, + const std::string& filename) +{ + const std::string cert_dir = get_cert_dir(); + const std::string file_path = cert_dir + "/" + filename; + auto* file = open_file(file_path, "wb"); + for (auto* x509 : certs) + { + if (!PEM_write_X509(file, x509)) + { + throw_error("Could not write x509"); + } + } + fclose(file); +} + +/* Self signed CA + certificate */ +static void generate_self_signed() +{ + auto* pkey = create_key(); + write_key(pkey, "galera_key.pem"); + auto* ca = create_x509(pkey, nullptr, "Galera Root"); + write_x509(ca, "galera_ca.pem"); + + auto* cert = create_x509(pkey, ca, "Galera Cert"); + write_x509(cert, "galera_cert.pem"); + X509_free(cert); + X509_free(ca); + EVP_PKEY_free(pkey); +} + +/* + ---- Server cert 1 + / + Root CA - Intermediate CA + \---- Server cert 2 + + Two bundles consisting of intermediate CA and server certificate + are created for servers 1 and 2. + */ +static void generate_chains() +{ + auto* root_ca_key = create_key(); + auto* root_ca = create_x509(root_ca_key, nullptr, "Galera Root CA"); + auto* int_ca_key = create_key(); + auto* int_ca = create_x509(int_ca_key, root_ca, "Galera Intermediate CA"); + + auto* server_1_key = create_key(); + auto* server_1_cert = create_x509(server_1_key, int_ca, "Galera Server 1"); + auto* server_2_key = create_key(); + auto* server_2_cert = create_x509(server_2_key, int_ca, "Galera Server 2"); + + write_x509(root_ca, "galera-ca.pem"); + write_key(server_1_key, "galera-server-1.key"); + write_x509_list({ server_1_cert, int_ca }, "bundle-galera-server-1.pem"); + write_key(server_2_key, "galera-server-2.key"); + write_x509_list({ server_2_cert, int_ca }, "bundle-galera-server-2.pem"); + + X509_free(server_2_cert); + EVP_PKEY_free(server_2_key); + X509_free(server_1_cert); + EVP_PKEY_free(server_1_key); + X509_free(int_ca); + EVP_PKEY_free(int_ca_key); + X509_free(root_ca); + EVP_PKEY_free(root_ca_key); +} + +static void generate_certificates() +{ + generate_self_signed(); + generate_chains(); +} + +// +// SSL +// + static gu::Config get_ssl_config() { gu::Config ret; @@ -1834,6 +2092,20 @@ END_TEST // Datagram // +/* Helper to determine if UDP sockets can be opened. */ +static bool have_datagram() try +{ + gu::AsioIoService io_service; + gu::URI uri("udp://127.0.0.1:0"); + auto socket(io_service.make_datagram_socket(uri)); + socket->open(uri); + return true; +} +catch (...) +{ + return false; +} + class MockDatagramSocketHandler : public gu::AsioDatagramSocketHandler { public: @@ -2173,6 +2445,7 @@ Suite* gu_asio_suite() // // SSL // + generate_certificates(); tc = tcase_create("test_ssl_io_service"); tcase_add_test(tc, test_ssl_io_service); @@ -2339,6 +2612,7 @@ Suite* gu_asio_suite() // // Datagram // + if (have_datagram()) { tc = tcase_create("test_datagram_socket"); tcase_add_test(tc, test_datagram_socket); @@ -2360,6 +2634,7 @@ Suite* gu_asio_suite() tcase_add_test(tc, test_datagram_send_to_and_async_read); suite_add_tcase(s, tc); + } #if defined(GALERA_ASIO_TEST_MULTICAST) tc = tcase_create("test_datagram_connect_multicast"); tcase_add_test(tc, test_datagram_connect_multicast); --- a/galera/src/CMakeLists.txt +++ b/galera/src/CMakeLists.txt @@ -127,7 +127,7 @@ endif() if (GALERA_VERSION_SCRIPT) add_custom_command(TARGET galera_smm POST_BUILD COMMAND - sh -c "! ${CMAKE_OBJDUMP} -T libgalera_smm.so | grep asio 1> /dev/null" + sh -c "! ${CMAKE_OBJDUMP} -T libgalera_smm.so | grep asio" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" COMMENT "Checking library symbol visibility (hidden)" VERBATIM) @@ -135,7 +135,7 @@ else() set(GALERA_LINK_OPTIONS "") add_custom_command(TARGET galera_smm POST_BUILD COMMAND - sh -c "${CMAKE_OBJDUMP} -T libgalera_smm.so | grep asio 1> /dev/null" + sh -c "${CMAKE_OBJDUMP} -T libgalera_smm.so | grep asio" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" COMMENT "Checking library symbol visibility (not hidden)" VERBATIM) @@ -145,7 +145,7 @@ if (NOT GALERA_WITH_SSL) message(STATUS "Building Galera without SSL") add_custom_command(TARGET galera_smm POST_BUILD COMMAND - sh -c "! (${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*crypto 1> /dev/null) && ! (${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*ssl 1> /dev/null)" + sh -c "! (${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*crypto) && ! (${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*ssl)" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" COMMENT "Verifying that library is not linked with SSL" VERBATIM) @@ -154,7 +154,7 @@ else() message(STATUS "Building Galera with static SSL") add_custom_command(TARGET galera_smm POST_BUILD COMMAND - sh -c "(${CMAKE_OBJDUMP} -t libgalera_smm.so | grep OPENSSL 1> /dev/null) && (${CMAKE_OBJDUMP} -t libgalera_smm.so | grep CRYPTO 1> /dev/null)" + sh -c "(${CMAKE_OBJDUMP} -t libgalera_smm.so | grep OPENSSL) && (${CMAKE_OBJDUMP} -t libgalera_smm.so | grep CRYPTO)" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" COMMENT "Verifying that library has OpenSSL linked statically" VERBATIM) @@ -162,7 +162,7 @@ else() message(STATUS "Building Galera with SSL") add_custom_command(TARGET galera_smm POST_BUILD COMMAND - sh -c "(${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*crypto 1> /dev/null) && (${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*ssl 1> /dev/null)" + sh -c "(${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*crypto) && (${CMAKE_OBJDUMP} -x libgalera_smm.so | grep NEEDED.*ssl)" COMMENT "Verifying that library is linked with SSL" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" VERBATIM)