// Copyright (c) 2020 - 2022 by Apex.AI Inc. All rights reserved. // Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // 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. // // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_hoofs/cxx/convert.hpp" #include "iceoryx_hoofs/cxx/type_traits.hpp" #include "iceoryx_hoofs/cxx/unique_ptr.hpp" #include "iceoryx_posh/internal/mepoo/mem_pool.hpp" #include "iceoryx_posh/internal/mepoo/memory_manager.hpp" #include "iceoryx_posh/mepoo/chunk_header.hpp" #include "test.hpp" #include namespace { using namespace ::testing; using namespace iox::mepoo; using UserPayloadOffset_t = ChunkHeader::UserPayloadOffset_t; TEST(ChunkHeader_test, ChunkHeaderHasInitializedMembers) { ::testing::Test::RecordProperty("TEST_ID", "b998bcb8-7db0-457d-a25a-86eae34f68dd"); constexpr uint32_t CHUNK_SIZE{753U}; constexpr uint32_t USER_PAYLOAD_SIZE{8U}; constexpr uint32_t USER_PAYLOAD_ALIGNMENT{iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); ChunkHeader sut{CHUNK_SIZE, chunkSettings}; EXPECT_THAT(sut.chunkSize(), Eq(CHUNK_SIZE)); // deliberately used a magic number to make the test fail when CHUNK_HEADER_VERSION changes EXPECT_THAT(sut.chunkHeaderVersion(), Eq(1U)); EXPECT_THAT(sut.originId(), Eq(iox::popo::UniquePortId(iox::popo::InvalidPortId))); EXPECT_THAT(sut.sequenceNumber(), Eq(0U)); EXPECT_THAT(sut.userHeaderId(), Eq(ChunkHeader::NO_USER_HEADER)); EXPECT_THAT(sut.userHeaderSize(), Eq(0U)); EXPECT_THAT(sut.userPayloadSize(), Eq(USER_PAYLOAD_SIZE)); EXPECT_THAT(sut.userPayloadAlignment(), Eq(USER_PAYLOAD_ALIGNMENT)); // a default created ChunkHeader has always an adjacent user-payload const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userPayloadStartAddress{reinterpret_cast(sut.userPayload())}; EXPECT_THAT(userPayloadStartAddress - chunkStartAddress, Eq(sizeof(ChunkHeader))); } TEST(ChunkHeader_test, ChunkHeaderBinaryCompatibilityCheck) { ::testing::Test::RecordProperty("TEST_ID", "8f88f81a-7e18-11ec-b34d-dd7741c14c43"); // NOTE: when this test fails and needs to be changed, // most probably one needs to increment the CHUNK_HEADER_VERSION // When this struct is touched, the CHUNK_HEADER_VERSION must be changed struct ExpectedChunkHeaderLayout { uint32_t chunkSize{0U}; uint8_t chunkHeaderVersion{0U}; uint8_t reserved{0U}; uint16_t userHeaderId{0}; uint64_t originId{0U}; uint64_t sequenceNumber{0U}; uint32_t userHeaderSize{0U}; uint32_t userPayloadSize{0U}; uint32_t userPayloadAlignment{0U}; uint32_t userPayloadOffset{0U}; }; constexpr auto EXPECTED_CHUNK_HEADER_VERSION{1U}; EXPECT_THAT(ChunkHeader::CHUNK_HEADER_VERSION, Eq(EXPECTED_CHUNK_HEADER_VERSION)); EXPECT_THAT(sizeof(ChunkHeader), Eq(sizeof(ExpectedChunkHeaderLayout))); EXPECT_THAT(alignof(ChunkHeader), Eq(alignof(ExpectedChunkHeaderLayout))); // The sut is always zeroized and a single member is set to a specific value // if a reinterpret cast to a ChunkHeader and access to the corresponding value // results in the previously set pattern, the layout matches and the ChunkHeader // did not change its ABI // Unfortunately offsetof cannot be used since the members of ChunkHeader are private ExpectedChunkHeaderLayout sut{}; auto zeroizeSut = [&] { std::memset(static_cast(&sut), 0, sizeof(ExpectedChunkHeaderLayout)); }; constexpr uint8_t PATTERN{42U}; #define IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(member) \ zeroizeSut(); \ sut.member = PATTERN; \ ASSERT_TRUE((std::is_same().member()), \ decltype(ExpectedChunkHeaderLayout::member)>::value)); \ EXPECT_THAT(reinterpret_cast(&sut)->member(), Eq(PATTERN)); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(chunkSize); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(chunkHeaderVersion); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(userHeaderId); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(sequenceNumber); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(userHeaderSize); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(userPayloadSize); IOX_TEST_CHUNK_HEADER_MEMBER_COMPATIBILITY(userPayloadAlignment); // special handling for originId since it is a UniquePortId zeroizeSut(); sut.originId = PATTERN; using OriginIdType = decltype(ExpectedChunkHeaderLayout::originId); ASSERT_TRUE((std::is_same().originId())::value_type, OriginIdType>::value)); auto originId = static_cast(reinterpret_cast(&sut)->originId()); EXPECT_THAT(originId, Eq(PATTERN)); // special handling for userPayloadOffset since it cannot easily be accessed zeroizeSut(); sut.userPayloadOffset = PATTERN; ASSERT_TRUE(( std::is_same::value)); auto userPayloadPointer = reinterpret_cast(&sut)->userPayload(); // this is a bit of a white box test but after all, all the other stuff in this test case is also white box auto userPayloadOffset = reinterpret_cast(userPayloadPointer) - reinterpret_cast(&sut); EXPECT_THAT(userPayloadOffset, Eq(static_cast(PATTERN))); } TEST(ChunkHeader_test, ChunkHeaderUserPayloadSizeTypeIsLargeEnoughForMempoolChunk) { ::testing::Test::RecordProperty("TEST_ID", "540e2e95-0890-4522-ae7f-c6d867679e0b"); using ChunkSize_t = iox::cxx::invoke_result::type; auto maxOfChunkSizeType = std::numeric_limits::max(); auto maxOfUserPayloadSizeType = std::numeric_limits().userPayloadSize())>::max(); // the user-payload will never be larger than the chunk // if the user-payload type can hold at least the maximum chunk size there will never be an overflow EXPECT_THAT(maxOfUserPayloadSizeType, Ge(maxOfChunkSizeType)); } TEST(ChunkHeader_test, UserPayloadFunctionCalledFromNonConstChunkHeaderWorks) { ::testing::Test::RecordProperty("TEST_ID", "d6b0fcce-8b49-429c-a1d2-c047b4b2e368"); constexpr uint32_t CHUNK_SIZE{753U}; constexpr uint32_t USER_PAYLOAD_SIZE{8U}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); ChunkHeader sut{CHUNK_SIZE, chunkSettings}; // a default created ChunkHeader has always an adjacent user-payload const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userPayloadStartAddress{reinterpret_cast(sut.userPayload())}; EXPECT_THAT(userPayloadStartAddress - chunkStartAddress, Eq(sizeof(ChunkHeader))); } TEST(ChunkHeader_test, UserPayloadFunctionCalledFromConstChunkHeaderWorks) { ::testing::Test::RecordProperty("TEST_ID", "091451db-09ed-4aa4-bcfe-d45c0c6d6c14"); constexpr uint32_t CHUNK_SIZE{753U}; constexpr uint32_t USER_PAYLOAD_SIZE{8U}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); const ChunkHeader sut{CHUNK_SIZE, chunkSettings}; // a default created ChunkHeader has always an adjacent user-payload const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userPayloadStartAddress{reinterpret_cast(sut.userPayload())}; EXPECT_THAT(userPayloadStartAddress - chunkStartAddress, Eq(sizeof(ChunkHeader))); } TEST(ChunkHeader_test, UserPayloadFunctionCalledFromNonConstChunkHeaderReturnsNonConstType) { ::testing::Test::RecordProperty("TEST_ID", "8876c866-2dda-4fb4-a96b-cee7f6b5e00b"); auto isNonConstReturn = std::is_same().userPayload()), void*>::value; EXPECT_TRUE(isNonConstReturn); } TEST(ChunkHeader_test, UserPayloadFunctionCalledFromConstChunkHeaderReturnsConstType) { ::testing::Test::RecordProperty("TEST_ID", "34957f49-06b3-426b-a0ba-df9071611fb2"); auto isConstReturn = std::is_same().userPayload()), const void*>::value; EXPECT_TRUE(isConstReturn); } TEST(ChunkHeader_test, UserHeaderFunctionCalledFromNonConstChunkHeaderWorks) { ::testing::Test::RecordProperty("TEST_ID", "2ddbb681-e8bb-42e0-9546-e9fd64c44be0"); alignas(ChunkHeader) static uint8_t storage[1024 * 1024]; constexpr uint32_t CHUNK_SIZE{753U}; constexpr uint32_t USER_PAYLOAD_SIZE{8U}; constexpr uint32_t USER_HEADER_SIZE{16U}; constexpr uint32_t USER_HEADER_ALIGNMENT{8U}; auto chunkSettingsResult = ChunkSettings::create( USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT, USER_HEADER_SIZE, USER_HEADER_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); iox::cxx::unique_ptr sut{new (storage) ChunkHeader(CHUNK_SIZE, chunkSettings), [](ChunkHeader*) {}}; // the user-header is always adjacent to the ChunkHeader const uint64_t chunkStartAddress{reinterpret_cast(sut.get())}; const uint64_t userHeaderStartAddress{reinterpret_cast(sut->userHeader())}; EXPECT_THAT(userHeaderStartAddress - chunkStartAddress, Eq(sizeof(ChunkHeader))); } TEST(ChunkHeader_test, UserHeaderFunctionCalledFromConstChunkHeaderWorks) { ::testing::Test::RecordProperty("TEST_ID", "04f902b9-0870-4ba4-b761-856e9bbfcd85"); alignas(ChunkHeader) static uint8_t storage[1024 * 1024]; constexpr uint32_t CHUNK_SIZE{753U}; constexpr uint32_t USER_PAYLOAD_SIZE{8U}; constexpr uint32_t USER_HEADER_SIZE{16U}; constexpr uint32_t USER_HEADER_ALIGNMENT{8U}; auto chunkSettingsResult = ChunkSettings::create( USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT, USER_HEADER_SIZE, USER_HEADER_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); iox::cxx::unique_ptr sut{new (storage) ChunkHeader(CHUNK_SIZE, chunkSettings), [](const ChunkHeader*) {}}; // the user-header is always adjacent to the ChunkHeader const uint64_t chunkStartAddress{reinterpret_cast(sut.get())}; const uint64_t userHeaderStartAddress{reinterpret_cast(sut->userHeader())}; EXPECT_THAT(userHeaderStartAddress - chunkStartAddress, Eq(sizeof(ChunkHeader))); } TEST(ChunkHeader_test, UserHeaderFunctionCalledFromNonConstChunkHeaderReturnsNonConstType) { ::testing::Test::RecordProperty("TEST_ID", "420fc560-c309-4fc2-9224-aec70c8212cc"); auto isNonConstReturn = std::is_same().userHeader()), void*>::value; EXPECT_TRUE(isNonConstReturn); } TEST(ChunkHeader_test, UserHeaderFunctionCalledFromConstChunkHeaderReturnsConstType) { ::testing::Test::RecordProperty("TEST_ID", "ca80c797-a22a-4d47-ba79-0f858b5d7836"); auto isConstReturn = std::is_same().userHeader()), const void*>::value; EXPECT_TRUE(isConstReturn); } TEST(ChunkHeader_test, FromUserPayloadFunctionCalledWithNullptrReturnsNullptr) { ::testing::Test::RecordProperty("TEST_ID", "9297471a-42ab-454c-9a10-ae2dd71e6bf9"); constexpr void* USER_PAYLOAD{nullptr}; auto chunkHeader = ChunkHeader::fromUserPayload(USER_PAYLOAD); EXPECT_THAT(chunkHeader, Eq(nullptr)); } TEST(ChunkHeader_test, FromUserPayloadFunctionCalledWithConstNullptrReturnsNullptr) { ::testing::Test::RecordProperty("TEST_ID", "de23b1ac-6552-4613-ac66-707d22157a0c"); constexpr const void* USER_PAYLOAD{nullptr}; auto chunkHeader = ChunkHeader::fromUserPayload(USER_PAYLOAD); EXPECT_THAT(chunkHeader, Eq(nullptr)); } TEST(ChunkHeader_test, FromUserPayloadFunctionCalledWithNonConstParamReturnsNonConstType) { ::testing::Test::RecordProperty("TEST_ID", "835d2c6e-490d-478d-a882-f51d113a73a3"); auto isNonConstReturn = std::is_same())), ChunkHeader*>::value; EXPECT_TRUE(isNonConstReturn); } TEST(ChunkHeader_test, FromUserPayloadFunctionCalledWithConstParamReturnsConstType) { ::testing::Test::RecordProperty("TEST_ID", "57ea3b52-fad4-4250-ab4d-276d5472edf1"); auto isConstReturn = std::is_same())), const ChunkHeader*>::value; EXPECT_TRUE(isConstReturn); } TEST(ChunkHeader_test, FromUserHeaderFunctionCalledWithNullptrReturnsNullptr) { ::testing::Test::RecordProperty("TEST_ID", "56fc6dd9-791b-4803-bd1a-5a0b53b0875d"); constexpr void* USER_HEADER{nullptr}; auto chunkHeader = ChunkHeader::fromUserHeader(USER_HEADER); EXPECT_THAT(chunkHeader, Eq(nullptr)); } TEST(ChunkHeader_test, FromUserHeaderFunctionCalledWithConstNullptrReturnsNullptr) { ::testing::Test::RecordProperty("TEST_ID", "f388ae41-7102-4d35-806e-4a572890e7a0"); constexpr const void* USER_HEADER{nullptr}; auto chunkHeader = ChunkHeader::fromUserHeader(USER_HEADER); EXPECT_THAT(chunkHeader, Eq(nullptr)); } TEST(ChunkHeader_test, FromUserHeaderFunctionCalledWithNonConstParamReturnsNonConstType) { ::testing::Test::RecordProperty("TEST_ID", "993df4e6-2cc2-4ae1-b4d4-01a4ded65ba4"); auto isNonConstReturn = std::is_same())), ChunkHeader*>::value; EXPECT_TRUE(isNonConstReturn); } TEST(ChunkHeader_test, FromUserHeaderFunctionCalledWithConstParamReturnsConstType) { ::testing::Test::RecordProperty("TEST_ID", "9b9a09b8-0baa-4a30-95e4-d081e01038d2"); auto isConstReturn = std::is_same())), const ChunkHeader*>::value; EXPECT_TRUE(isConstReturn); } TEST(ChunkHeader_test, UsedChunkSizeIsSizeOfChunkHeaderWhenUserPayloadIsZero) { ::testing::Test::RecordProperty("TEST_ID", "67d3907e-5090-4814-b726-2a37e8780395"); constexpr uint32_t CHUNK_SIZE{2 * sizeof(ChunkHeader)}; constexpr uint32_t USER_PAYLOAD_SIZE{0U}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); ChunkHeader sut{CHUNK_SIZE, chunkSettings}; EXPECT_THAT(sut.usedSizeOfChunk(), Eq(sizeof(ChunkHeader))); } TEST(ChunkHeader_test, UsedChunkSizeIsSizeOfChunkHeaderPlusOneWhenUserPayloadIsOne) { ::testing::Test::RecordProperty("TEST_ID", "5a0cd3a1-3edc-441a-9ba6-486a463ad9d7"); constexpr uint32_t CHUNK_SIZE{2 * sizeof(ChunkHeader)}; constexpr uint32_t USER_PAYLOAD_SIZE{1U}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); ChunkHeader sut{CHUNK_SIZE, chunkSettings}; EXPECT_THAT(sut.usedSizeOfChunk(), Eq(sizeof(ChunkHeader) + USER_PAYLOAD_SIZE)); } TEST(ChunkHeader_test, ConstructorTerminatesWhenUserPayloadSizeExceedsChunkSize) { ::testing::Test::RecordProperty("TEST_ID", "c8f911eb-ed0d-495a-8858-9fc45f5a06e8"); constexpr uint32_t CHUNK_SIZE{128U}; constexpr uint32_t USER_PAYLOAD_SIZE{2U * CHUNK_SIZE}; auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); EXPECT_DEATH({ ChunkHeader sut(CHUNK_SIZE, chunkSettings); }, ".*"); } // BEGIN PARAMETERIZED TESTS FOR CHUNK HEADER struct PayloadParams { uint32_t size{0U}; uint32_t alignment{iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT}; }; void createChunksOnMultipleAddresses(const PayloadParams& userPayloadParams, const uint32_t userHeaderSize, const uint32_t userHeaderAlignment, const std::function testHook) { ASSERT_TRUE(testHook); constexpr size_t MAX_USER_PAYLOAD_ALIGNMENT_FOR_TEST{512U}; ASSERT_THAT(MAX_USER_PAYLOAD_ALIGNMENT_FOR_TEST, Gt(alignof(ChunkHeader))); constexpr size_t STORAGE_ALIGNMENT{2 * MAX_USER_PAYLOAD_ALIGNMENT_FOR_TEST}; alignas(STORAGE_ALIGNMENT) static uint8_t storage[1024 * 1024]; ASSERT_THAT(reinterpret_cast(storage) % STORAGE_ALIGNMENT, Eq(0U)); // storage alignment boundaries -> ⊥ ⊥ ⊥ ⊥ // max user-payload alignment for test boundaries -> ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ // ChunkHeader alignment boundaries -> ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ ⊥ // the test creates ChunkHeader on multiple address boundaries in order to have all possible scenarios up to a // user-payload boundary of 512; this boundary is more than large enough since a user-payload alignment of 2 times // the ChunkHeader alignment would already be sufficient to test all corner cases for (auto alignedChunkAddress = alignof(ChunkHeader); alignedChunkAddress <= MAX_USER_PAYLOAD_ALIGNMENT_FOR_TEST; alignedChunkAddress += alignof(ChunkHeader)) { SCOPED_TRACE(std::string("Chunk placed on address ") + iox::cxx::convert::toString(alignedChunkAddress)); auto chunkSettingsResult = ChunkSettings::create( userPayloadParams.size, userPayloadParams.alignment, userHeaderSize, userHeaderAlignment); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); auto chunkSize = chunkSettings.requiredChunkSize(); auto chunkHeader = new (&storage[alignedChunkAddress]) ChunkHeader(chunkSize, chunkSettings); testHook(*chunkHeader); chunkHeader->~ChunkHeader(); } } void checkUserHeaderIdAndSizeAndPayloadSizeAndAlignmentIsSet(const ChunkHeader& sut, const PayloadParams& userPayloadParams, const uint16_t userHeaderId, const uint32_t userHeaderSize) { SCOPED_TRACE(std::string("Check user-header id and size and user-payload alignment ist correctly set")); EXPECT_EQ(sut.userPayloadSize(), userPayloadParams.size); // a user-payload alignment of zero will internally be set to one auto adjustedAlignment = userPayloadParams.alignment == 0U ? 1U : userPayloadParams.alignment; EXPECT_EQ(sut.userPayloadAlignment(), adjustedAlignment); EXPECT_EQ(sut.userHeaderId(), userHeaderId); EXPECT_EQ(sut.userHeaderSize(), userHeaderSize); } void checkUserPayloadNotOverlappingWithChunkHeader(const ChunkHeader& sut) { SCOPED_TRACE(std::string("Check user-payload not overlapping with ChunkHeader")); const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userPayloadStartAddress{reinterpret_cast(sut.userPayload())}; EXPECT_THAT(userPayloadStartAddress - chunkStartAddress, Ge(sizeof(ChunkHeader))); } void checkUserPayloadNotOverlappingWithUserHeader(const ChunkHeader& sut, const uint32_t userHeaderSize) { SCOPED_TRACE(std::string("Check user-payload not overlapping with user-header")); const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userPayloadStartAddress{reinterpret_cast(sut.userPayload())}; const uint64_t userHeaderSizeAndPadding{ iox::algorithm::max(userHeaderSize, static_cast(alignof(UserPayloadOffset_t)))}; constexpr uint64_t BACK_OFFSET_SIZE{sizeof(UserPayloadOffset_t)}; const uint64_t expectedRequiredSpace{sizeof(ChunkHeader) + userHeaderSizeAndPadding + BACK_OFFSET_SIZE}; EXPECT_THAT(userPayloadStartAddress - chunkStartAddress, Ge(expectedRequiredSpace)); } void checkUserHeaderIsAdjacentToChunkHeader(const ChunkHeader& sut) { SCOPED_TRACE(std::string("Check user-header is adjacent to ChunkHeader")); const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userHeaderStartAddress{reinterpret_cast(sut.userHeader())}; EXPECT_EQ(userHeaderStartAddress - chunkStartAddress, sizeof(ChunkHeader)); } void checkUserPayloadSize(const ChunkHeader& sut, const PayloadParams& userPayloadParams) { SCOPED_TRACE(std::string("Check user-payload size")); EXPECT_EQ(sut.userPayloadSize(), userPayloadParams.size); } void checkUserPayloadAlignment(const ChunkHeader& sut, const PayloadParams& userPayloadParams) { SCOPED_TRACE(std::string("Check user-payload alignment")); // a user-payload alignment of zero will internally be set to one auto adjustedAlignment = userPayloadParams.alignment == 0U ? 1U : userPayloadParams.alignment; EXPECT_EQ(reinterpret_cast(sut.userPayload()) % adjustedAlignment, 0U); } void checkUsedSizeOfChunk(const ChunkHeader& sut, const PayloadParams& userPayloadParams) { SCOPED_TRACE(std::string("Check used size of chunk")); const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; const uint64_t userPayloadStartAddress{reinterpret_cast(sut.userPayload())}; const uint64_t expectedUsedSizeOfChunk{userPayloadStartAddress + userPayloadParams.size - chunkStartAddress}; EXPECT_EQ(sut.usedSizeOfChunk(), expectedUsedSizeOfChunk); EXPECT_THAT(sut.usedSizeOfChunk(), Le(sut.chunkSize())); } void checkConversionOfUserPayloadPointerToChunkHeader(const ChunkHeader& sut) { SCOPED_TRACE(std::string("Check conversion of user-payload pointer to ChunkHeader pointer")); const auto userPayload = sut.userPayload(); EXPECT_EQ(ChunkHeader::fromUserPayload(userPayload), &sut); } void checkConversionOfUserHeaderPointerToChunkHeader(const ChunkHeader& sut) { SCOPED_TRACE(std::string("Check conversion of user-Header pointer to ChunkHeader pointer")); const auto userHeader = sut.userHeader(); EXPECT_EQ(ChunkHeader::fromUserHeader(userHeader), &sut); } class ChunkHeader_AlteringUserPayloadWithoutUserHeader : public ::testing::TestWithParam { }; // without a user-header, the user-payload is located right after the ChunkHeader, therefore the payload size and // alignment parameters are made dependant on the ChunkHeader INSTANTIATE_TEST_SUITE_P(ChunkHeader_test, ChunkHeader_AlteringUserPayloadWithoutUserHeader, ::testing::Values( // alignment = 0 PayloadParams{0U, 0U}, PayloadParams{1U, 0U}, PayloadParams{sizeof(ChunkHeader), 0U}, PayloadParams{sizeof(ChunkHeader) * 42U, 0U}, // alignment = 1 PayloadParams{0U, 1U}, PayloadParams{1U, 1U}, PayloadParams{sizeof(ChunkHeader), 1U}, PayloadParams{sizeof(ChunkHeader) * 42U, 1U}, // alignment = alignof(ChunkHeader) / 2 PayloadParams{0U, alignof(ChunkHeader) / 2}, PayloadParams{1U, alignof(ChunkHeader) / 2}, PayloadParams{sizeof(ChunkHeader), alignof(ChunkHeader) / 2}, PayloadParams{sizeof(ChunkHeader) * 42U, alignof(ChunkHeader) / 2}, // alignment = alignof(ChunkHeader) PayloadParams{0U, alignof(ChunkHeader)}, PayloadParams{1U, alignof(ChunkHeader)}, PayloadParams{sizeof(ChunkHeader), alignof(ChunkHeader)}, PayloadParams{sizeof(ChunkHeader) * 42U, alignof(ChunkHeader)}, // alignment = alignof(ChunkHeader) * 2 PayloadParams{0U, alignof(ChunkHeader) * 2}, PayloadParams{1U, alignof(ChunkHeader) * 2}, PayloadParams{sizeof(ChunkHeader), alignof(ChunkHeader) * 2}, PayloadParams{sizeof(ChunkHeader) * 42U, alignof(ChunkHeader) * 2})); TEST_P(ChunkHeader_AlteringUserPayloadWithoutUserHeader, CheckIntegrityOfChunkHeaderWithoutUserHeader) { ::testing::Test::RecordProperty("TEST_ID", "e80e27f1-de7c-4be4-a83e-9f0eb766de12"); const auto userPayloadParams = GetParam(); SCOPED_TRACE(std::string("User-Payload: size = ") + iox::cxx::convert::toString(userPayloadParams.size) + std::string("; alignment = ") + iox::cxx::convert::toString(userPayloadParams.alignment)); constexpr uint32_t USER_HEADER_SIZE{iox::CHUNK_NO_USER_HEADER_SIZE}; constexpr uint32_t USER_HEADER_ALIGNMENT{iox::CHUNK_NO_USER_HEADER_ALIGNMENT}; createChunksOnMultipleAddresses(userPayloadParams, USER_HEADER_SIZE, USER_HEADER_ALIGNMENT, [&](ChunkHeader& sut) { checkUserHeaderIdAndSizeAndPayloadSizeAndAlignmentIsSet( sut, userPayloadParams, ChunkHeader::NO_USER_HEADER, USER_HEADER_SIZE); checkUserPayloadNotOverlappingWithChunkHeader(sut); checkUserPayloadSize(sut, userPayloadParams); checkUserPayloadAlignment(sut, userPayloadParams); checkUsedSizeOfChunk(sut, userPayloadParams); checkConversionOfUserPayloadPointerToChunkHeader(sut); }); } class ChunkHeader_AlteringUserPayloadWithUserHeader : public ::testing::TestWithParam { }; // with a user-header, the user-payload is located right after the UserPayloadOffset_t, therefore the user-payload size // and alignment parameters are made dependant on the UserPayloadOffset_t INSTANTIATE_TEST_SUITE_P(ChunkHeader_test, ChunkHeader_AlteringUserPayloadWithUserHeader, ::testing::Values( // alignment = 0 PayloadParams{0U, 0U}, PayloadParams{1U, 0U}, PayloadParams{sizeof(UserPayloadOffset_t), 0U}, PayloadParams{sizeof(UserPayloadOffset_t) * 42U, 0U}, // alignment = 1 PayloadParams{0U, 1U}, PayloadParams{1U, 1U}, PayloadParams{sizeof(UserPayloadOffset_t), 1U}, PayloadParams{sizeof(UserPayloadOffset_t) * 42U, 1U}, // alignment = alignof(UserPayloadOffset_t) / 2 PayloadParams{0U, alignof(UserPayloadOffset_t) / 2}, PayloadParams{1U, alignof(UserPayloadOffset_t) / 2}, PayloadParams{sizeof(UserPayloadOffset_t), alignof(UserPayloadOffset_t) / 2}, PayloadParams{sizeof(UserPayloadOffset_t) * 42U, alignof(UserPayloadOffset_t) / 2}, // alignment = alignof(UserPayloadOffset_t) PayloadParams{0U, alignof(UserPayloadOffset_t)}, PayloadParams{1U, alignof(UserPayloadOffset_t)}, PayloadParams{sizeof(UserPayloadOffset_t), alignof(UserPayloadOffset_t)}, PayloadParams{sizeof(UserPayloadOffset_t) * 42U, alignof(UserPayloadOffset_t)}, // alignment = alignof(UserPayloadOffset_t) * 2 PayloadParams{0U, alignof(UserPayloadOffset_t) * 2}, PayloadParams{1U, alignof(UserPayloadOffset_t) * 2}, PayloadParams{sizeof(UserPayloadOffset_t), alignof(UserPayloadOffset_t) * 2}, PayloadParams{sizeof(UserPayloadOffset_t) * 42U, alignof(UserPayloadOffset_t) * 2})); TEST_P(ChunkHeader_AlteringUserPayloadWithUserHeader, CheckIntegrityOfChunkHeaderWithUserHeader) { ::testing::Test::RecordProperty("TEST_ID", "a0d45cb6-cf0c-44e7-b759-fdc25535c9c4"); const auto userPayloadParams = GetParam(); SCOPED_TRACE(std::string("User-Payload: size = ") + iox::cxx::convert::toString(userPayloadParams.size) + std::string("; alignment = ") + iox::cxx::convert::toString(userPayloadParams.alignment)); constexpr uint32_t SMALL_USER_HEADER{alignof(ChunkHeader)}; static_assert(SMALL_USER_HEADER < sizeof(ChunkHeader), "For this test the size must be smaller than ChunkHeader"); constexpr uint32_t USER_HEADER_SIZES[]{1U, SMALL_USER_HEADER, sizeof(ChunkHeader), sizeof(ChunkHeader) * 2U}; constexpr uint32_t USER_HEADER_ALIGNMENTS[]{0U, 1U, alignof(ChunkHeader) / 2U, alignof(ChunkHeader)}; for (const auto userHeaderAlignment : USER_HEADER_ALIGNMENTS) { SCOPED_TRACE(std::string("User-Header alignment = ") + iox::cxx::convert::toString(userHeaderAlignment)); for (const auto userHeaderSize : USER_HEADER_SIZES) { SCOPED_TRACE(std::string("User-Header size = ") + iox::cxx::convert::toString(userHeaderSize)); if (userHeaderSize < userHeaderAlignment) { // the size must always be a multiple of the alignment continue; } createChunksOnMultipleAddresses( userPayloadParams, userHeaderSize, userHeaderAlignment, [&](ChunkHeader& sut) { checkUserHeaderIdAndSizeAndPayloadSizeAndAlignmentIsSet( sut, userPayloadParams, ChunkHeader::UNKNOWN_USER_HEADER, userHeaderSize); checkUserHeaderIsAdjacentToChunkHeader(sut); checkUserPayloadNotOverlappingWithUserHeader(sut, userHeaderSize); checkUserPayloadSize(sut, userPayloadParams); checkUserPayloadAlignment(sut, userPayloadParams); checkUsedSizeOfChunk(sut, userPayloadParams); checkConversionOfUserPayloadPointerToChunkHeader(sut); checkConversionOfUserHeaderPointerToChunkHeader(sut); }); } } } // END PARAMETERIZED TESTS FOR CHUNK HEADER } // namespace