Merge "drm vts 1.2 refactor"

This commit is contained in:
Robert Shih 2020-01-30 03:55:29 +00:00 committed by Gerrit Code Review
commit 568b391009
7 changed files with 170 additions and 236 deletions

View file

@ -14,27 +14,61 @@
// limitations under the License.
//
cc_test {
name: "VtsHalDrmV1_2TargetTest",
cc_library_static {
name: "android.hardware.drm@1.2-vts",
defaults: ["VtsHalTargetTestDefaults"],
include_dirs: ["hardware/interfaces/drm/1.0/vts/functional"],
local_include_dirs: [
"include",
],
srcs: [
"drm_hal_clearkey_module.cpp",
"drm_hal_common.cpp",
"drm_hal_test.cpp",
"vendor_modules.cpp",
],
static_libs: [
shared_libs: [
"android.hardware.drm@1.0",
"android.hardware.drm@1.1",
"android.hardware.drm@1.2",
"android.hardware.drm@1.0-helper",
"android.hidl.allocator@1.0",
"android.hidl.memory@1.0",
"libhidlmemory",
"libnativehelper",
"libssl",
],
static_libs: [
"android.hardware.drm@1.0-helper",
"libcrypto_static",
"libdrmvtshelper",
],
export_shared_lib_headers: [
"android.hardware.drm@1.2",
],
export_static_lib_headers: [
"android.hardware.drm@1.0-helper",
],
export_include_dirs: [
"include",
],
}
cc_test {
name: "VtsHalDrmV1_2TargetTest",
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"drm_hal_test_main.cpp",
],
whole_static_libs: [
"android.hardware.drm@1.2-vts",
],
shared_libs: [
"android.hardware.drm@1.0",
"android.hardware.drm@1.2",
"android.hidl.allocator@1.0",
"libhidlmemory",
],
static_libs: [
"android.hardware.drm@1.0-helper",
"libcrypto_static",
"libdrmvtshelper",
],
test_suites: [
"general-tests",

View file

@ -26,7 +26,7 @@
#include <random>
#include "drm_hal_clearkey_module.h"
#include "drm_hal_common.h"
#include "android/hardware/drm/1.2/vts/drm_hal_common.h"
using ::android::hardware::drm::V1_0::BufferType;
using ::android::hardware::drm::V1_0::DestinationBuffer;
@ -94,7 +94,7 @@ static DrmHalVTSVendorModule_V1* getModuleForInstance(const std::string& instanc
* DrmHalTest
*/
DrmHalTest::DrmHalTest() : vendorModule(getModuleForInstance(GetParam())) {}
DrmHalTest::DrmHalTest() : vendorModule(getModuleForInstance(GetParamService())) {}
void DrmHalTest::SetUp() {
const ::testing::TestInfo* const test_info =
@ -102,9 +102,9 @@ void DrmHalTest::SetUp() {
ALOGD("Running test %s.%s from (vendor) module %s",
test_info->test_case_name(), test_info->name(),
GetParam().c_str());
GetParamService().c_str());
const string instance = GetParam();
const string instance = GetParamService();
drmFactory = IDrmFactory::getService(instance);
ASSERT_NE(drmFactory, nullptr);
@ -124,8 +124,12 @@ void DrmHalTest::SetUp() {
contentConfigurations = vendorModule->getContentConfigurations();
// If drm scheme not installed skip subsequent tests
if (!drmFactory->isCryptoSchemeSupported(getVendorUUID())) {
GTEST_SKIP() << "vendor module drm scheme not supported";
if (!drmFactory->isCryptoSchemeSupported(getUUID())) {
if (GetParamUUID() == hidl_array<uint8_t, 16>()) {
GTEST_SKIP() << "vendor module drm scheme not supported";
} else {
FAIL() << "param scheme must be supported: " << android::hardware::toString(GetParamUUID());
}
}
ASSERT_NE(nullptr, drmPlugin.get()) << "Can't find " << vendorModule->getServiceName() << " drm@1.2 plugin";
@ -140,7 +144,7 @@ sp<IDrmPlugin> DrmHalTest::createDrmPlugin() {
sp<IDrmPlugin> plugin = nullptr;
hidl_string packageName("android.hardware.drm.test");
auto res =
drmFactory->createPlugin(getVendorUUID(), packageName,
drmFactory->createPlugin(getUUID(), packageName,
[&](StatusV1_0 status, const sp<IDrmPluginV1_0>& pluginV1_0) {
EXPECT_EQ(StatusV1_0::OK == status, pluginV1_0 != nullptr);
plugin = IDrmPlugin::castFrom(pluginV1_0);
@ -159,7 +163,7 @@ sp<ICryptoPlugin> DrmHalTest::createCryptoPlugin() {
sp<ICryptoPlugin> plugin = nullptr;
hidl_vec<uint8_t> initVec;
auto res = cryptoFactory->createPlugin(
getVendorUUID(), initVec,
getUUID(), initVec,
[&](StatusV1_0 status, const sp<ICryptoPluginV1_0>& pluginV1_0) {
EXPECT_EQ(StatusV1_0::OK == status, pluginV1_0 != nullptr);
plugin = ICryptoPlugin::castFrom(pluginV1_0);
@ -170,6 +174,13 @@ sp<ICryptoPlugin> DrmHalTest::createCryptoPlugin() {
return plugin;
}
hidl_array<uint8_t, 16> DrmHalTest::getUUID() {
if (GetParamUUID() == hidl_array<uint8_t, 16>()) {
return getVendorUUID();
}
return GetParamUUID();
}
hidl_array<uint8_t, 16> DrmHalTest::getVendorUUID() {
if (vendorModule == nullptr) return {};
vector<uint8_t> uuid = vendorModule->getUUID();
@ -509,7 +520,7 @@ void DrmHalTest::aes_cbc_decrypt(uint8_t* dest, uint8_t* src,
/**
* Helper method to test decryption with invalid keys is returned
*/
void DrmHalClearkeyTest::decryptWithInvalidKeys(
void DrmHalClearkeyTestV1_2::decryptWithInvalidKeys(
hidl_vec<uint8_t>& invalidResponse,
vector<uint8_t>& iv,
const Pattern& noPattern,

View file

@ -17,13 +17,13 @@
#define LOG_TAG "drm_hal_test@1.2"
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/HidlSupport.h>
#include <hidl/ServiceManagement.h>
#include <log/log.h>
#include <openssl/aes.h>
#include <vector>
#include "drm_hal_common.h"
#include "android/hardware/drm/1.2/vts/drm_hal_common.h"
using ::android::hardware::drm::V1_0::Status;
using ::android::hardware::drm::V1_1::KeyRequestType;
@ -34,12 +34,13 @@ using ::android::hardware::drm::V1_2::KeyStatus;
using ::android::hardware::drm::V1_2::KeyStatusType;
using ::android::hardware::drm::V1_2::OfflineLicenseState;
using ::android::hardware::drm::V1_2::vts::DrmHalClearkeyTest;
using ::android::hardware::drm::V1_2::vts::DrmHalClearkeyTestV1_2;
using ::android::hardware::drm::V1_2::vts::DrmHalPluginListener;
using ::android::hardware::drm::V1_2::vts::DrmHalTest;
using ::android::hardware::drm::V1_2::vts::kCallbackLostState;
using ::android::hardware::drm::V1_2::vts::kCallbackKeysChange;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_string;
static const char* const kVideoMp4 = "video/mp4";
@ -54,7 +55,7 @@ static const SecurityLevel kHwSecureAll = SecurityLevel::HW_SECURE_ALL;
* Ensure drm factory supports module UUID Scheme
*/
TEST_P(DrmHalTest, VendorUuidSupported) {
auto res = drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kSwSecureCrypto);
auto res = drmFactory->isCryptoSchemeSupported_1_2(getUUID(), kVideoMp4, kSwSecureCrypto);
ALOGI("kVideoMp4 = %s res %d", kVideoMp4, (bool)res);
EXPECT_TRUE(res);
}
@ -82,7 +83,7 @@ TEST_P(DrmHalTest, EmptyPluginUUIDNotSupported) {
* Ensure drm factory doesn't support an invalid mime type
*/
TEST_P(DrmHalTest, BadMimeNotSupported) {
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kBadMime, kSwSecureCrypto));
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getUUID(), kBadMime, kSwSecureCrypto));
}
/**
@ -398,14 +399,14 @@ TEST_P(DrmHalTest, EncryptedAesCtrSegmentTestNoKeys) {
/**
* Ensure clearkey drm factory doesn't support security level higher than supported
*/
TEST_P(DrmHalClearkeyTest, BadLevelNotSupported) {
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kHwSecureAll));
TEST_P(DrmHalClearkeyTestV1_2, BadLevelNotSupported) {
EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getUUID(), kVideoMp4, kHwSecureAll));
}
/**
* Test resource contention during attempt to generate key request
*/
TEST_P(DrmHalClearkeyTest, GetKeyRequestResourceContention) {
TEST_P(DrmHalClearkeyTestV1_2, GetKeyRequestResourceContention) {
Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorResourceContention);
EXPECT_EQ(Status::OK, status);
auto sessionId = openSession();
@ -426,7 +427,7 @@ TEST_P(DrmHalClearkeyTest, GetKeyRequestResourceContention) {
/**
* Test clearkey plugin offline key with mock error
*/
TEST_P(DrmHalClearkeyTest, OfflineLicenseInvalidState) {
TEST_P(DrmHalClearkeyTestV1_2, OfflineLicenseInvalidState) {
auto sessionId = openSession();
hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorInvalidState);
@ -447,7 +448,7 @@ TEST_P(DrmHalClearkeyTest, OfflineLicenseInvalidState) {
/**
* Test SessionLostState is triggered on error
*/
TEST_P(DrmHalClearkeyTest, SessionLostState) {
TEST_P(DrmHalClearkeyTestV1_2, SessionLostState) {
sp<DrmHalPluginListener> listener = new DrmHalPluginListener();
auto res = drmPlugin->setListener(listener);
EXPECT_OK(res);
@ -467,7 +468,7 @@ TEST_P(DrmHalClearkeyTest, SessionLostState) {
/**
* Negative decrypt test. Decrypt with invalid key.
*/
TEST_P(DrmHalClearkeyTest, DecryptWithEmptyKey) {
TEST_P(DrmHalClearkeyTestV1_2, DecryptWithEmptyKey) {
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const uint32_t kClearBytes = 512;
@ -504,7 +505,7 @@ TEST_P(DrmHalClearkeyTest, DecryptWithEmptyKey) {
/**
* Negative decrypt test. Decrypt with a key exceeds AES_BLOCK_SIZE.
*/
TEST_P(DrmHalClearkeyTest, DecryptWithKeyTooLong) {
TEST_P(DrmHalClearkeyTestV1_2, DecryptWithKeyTooLong) {
vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
const Pattern noPattern = {0, 0};
const uint32_t kClearBytes = 512;
@ -531,43 +532,3 @@ TEST_P(DrmHalClearkeyTest, DecryptWithKeyTooLong) {
memcpy(invalidResponse.data(), keyTooLongResponse.c_str(), kKeyTooLongResponseSize);
decryptWithInvalidKeys(invalidResponse, iv, noPattern, subSamples);
}
/**
* Instantiate the set of test cases for each vendor module
*/
static const std::set<std::string> kAllInstances = [] {
using ::android::hardware::drm::V1_2::ICryptoFactory;
using ::android::hardware::drm::V1_2::IDrmFactory;
std::vector<std::string> drmInstances =
android::hardware::getAllHalInstanceNames(IDrmFactory::descriptor);
std::vector<std::string> cryptoInstances =
android::hardware::getAllHalInstanceNames(ICryptoFactory::descriptor);
std::set<std::string> allInstances;
allInstances.insert(drmInstances.begin(), drmInstances.end());
allInstances.insert(cryptoInstances.begin(), cryptoInstances.end());
return allInstances;
}();
INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalTest, testing::ValuesIn(kAllInstances),
android::hardware::PrintInstanceNameToString);
INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalClearkeyTest, testing::ValuesIn(kAllInstances),
android::hardware::PrintInstanceNameToString);
int main(int argc, char** argv) {
#if defined(__LP64__)
const char* kModulePath = "/data/local/tmp/64/lib";
#else
const char* kModulePath = "/data/local/tmp/32/lib";
#endif
DrmHalTest::gVendorModules = new drm_vts::VendorModules(kModulePath);
if (DrmHalTest::gVendorModules->getPathList().size() == 0) {
std::cerr << "WARNING: No vendor modules found in " << kModulePath <<
", all vendor tests will be skipped" << std::endl;
}
::testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
ALOGI("Test result = %d", status);
return status;
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.
*/
/**
* Instantiate the set of test cases for each vendor module
*/
#define LOG_TAG "drm_hal_test@1.2"
#include <gtest/gtest.h>
#include <hidl/HidlSupport.h>
#include <hidl/ServiceManagement.h>
#include <log/log.h>
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "android/hardware/drm/1.2/vts/drm_hal_common.h"
using android::hardware::drm::V1_2::vts::DrmHalTest;
using android::hardware::drm::V1_2::vts::DrmHalClearkeyTestV1_2;
using drm_vts::DrmHalTestParam;
using drm_vts::PrintParamInstanceToString;
static const std::vector<DrmHalTestParam> kAllInstances = [] {
using ::android::hardware::drm::V1_2::ICryptoFactory;
using ::android::hardware::drm::V1_2::IDrmFactory;
std::vector<std::string> drmInstances =
android::hardware::getAllHalInstanceNames(IDrmFactory::descriptor);
std::vector<std::string> cryptoInstances =
android::hardware::getAllHalInstanceNames(ICryptoFactory::descriptor);
std::set<std::string> allInstances;
allInstances.insert(drmInstances.begin(), drmInstances.end());
allInstances.insert(cryptoInstances.begin(), cryptoInstances.end());
std::vector<DrmHalTestParam> allInstanceUuidCombos;
auto noUUID = [](std::string s) { return DrmHalTestParam(s); };
std::transform(allInstances.begin(), allInstances.end(),
std::back_inserter(allInstanceUuidCombos), noUUID);
return allInstanceUuidCombos;
}();
INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalTest, testing::ValuesIn(kAllInstances),
PrintParamInstanceToString);
INSTANTIATE_TEST_SUITE_P(PerInstance, DrmHalClearkeyTestV1_2, testing::ValuesIn(kAllInstances),
PrintParamInstanceToString);
int main(int argc, char** argv) {
#if defined(__LP64__)
const char* kModulePath = "/data/local/tmp/64/lib";
#else
const char* kModulePath = "/data/local/tmp/32/lib";
#endif
DrmHalTest::gVendorModules = new drm_vts::VendorModules(kModulePath);
if (DrmHalTest::gVendorModules->getPathList().size() == 0) {
std::cerr << "WARNING: No vendor modules found in " << kModulePath <<
", all vendor tests will be skipped" << std::endl;
}
::testing::InitGoogleTest(&argc, argv);
int status = RUN_ALL_TESTS();
ALOGI("Test result = %d", status);
return status;
}

View file

@ -23,14 +23,19 @@
#include <android/hardware/drm/1.2/IDrmPlugin.h>
#include <android/hardware/drm/1.2/IDrmPluginListener.h>
#include <android/hardware/drm/1.2/types.h>
#include <hidl/HidlSupport.h>
#include <array>
#include <chrono>
# include <iostream>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "drm_hal_vendor_module_api.h"
#include "drm_vts_helper.h"
#include "vendor_modules.h"
#include "VtsHalHidlTargetCallbackBase.h"
@ -41,13 +46,10 @@ using ::android::hardware::drm::V1_0::Mode;
using ::android::hardware::drm::V1_0::Pattern;
using ::android::hardware::drm::V1_0::SessionId;
using ::android::hardware::drm::V1_0::SubSample;
using ::android::hardware::drm::V1_1::SecurityLevel;
using KeyStatusV1_0 = ::android::hardware::drm::V1_0::KeyStatus;
using StatusV1_0 = ::android::hardware::drm::V1_0::Status;
using ::android::hardware::drm::V1_1::ICryptoFactory;
using ::android::hardware::drm::V1_1::SecurityLevel;
using StatusV1_2 = ::android::hardware::drm::V1_2::Status;
using ::android::hardware::hidl_array;
@ -58,7 +60,11 @@ using ::android::hardware::Void;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::sp;
using drm_vts::DrmHalTestParam;
using std::array;
using std::map;
using std::pair;
using std::string;
using std::unique_ptr;
using std::vector;
@ -71,7 +77,7 @@ namespace drm {
namespace V1_2 {
namespace vts {
class DrmHalTest : public ::testing::TestWithParam<std::string> {
class DrmHalTest : public ::testing::TestWithParam<DrmHalTestParam> {
public:
static drm_vts::VendorModules* gVendorModules;
DrmHalTest();
@ -79,7 +85,10 @@ class DrmHalTest : public ::testing::TestWithParam<std::string> {
virtual void TearDown() override {}
protected:
hidl_array<uint8_t, 16> getUUID();
hidl_array<uint8_t, 16> getVendorUUID();
hidl_array<uint8_t, 16> GetParamUUID() { return GetParam().scheme_; }
string GetParamService() { return GetParam().instance_; }
void provision();
SessionId openSession(SecurityLevel level, StatusV1_0* err);
SessionId openSession();
@ -124,7 +133,7 @@ class DrmHalTest : public ::testing::TestWithParam<std::string> {
};
class DrmHalClearkeyTest : public DrmHalTest {
class DrmHalClearkeyTestV1_2 : public DrmHalTest {
public:
virtual void SetUp() override {
DrmHalTest::SetUp();
@ -132,7 +141,7 @@ class DrmHalClearkeyTest : public DrmHalTest {
0xE2, 0x71, 0x9D, 0x58, 0xA9, 0x85, 0xB3, 0xC9,
0x78, 0x1A, 0xB0, 0x30, 0xAF, 0x78, 0xD3, 0x0E};
if (!drmFactory->isCryptoSchemeSupported(kClearKeyUUID)) {
GTEST_SKIP() << "ClearKey not supported by " << GetParam();
GTEST_SKIP() << "ClearKey not supported by " << GetParamService();
}
}
virtual void TearDown() override {}

View file

@ -1,83 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "drm-vts-vendor-modules"
#include <dirent.h>
#include <dlfcn.h>
#include <log/log.h>
#include <memory>
#include <utils/String8.h>
#include <SharedLibrary.h>
#include "drm_hal_vendor_module_api.h"
#include "vendor_modules.h"
using std::string;
using std::vector;
using std::unique_ptr;
using ::android::String8;
using ::android::hardware::drm::V1_0::helper::SharedLibrary;
namespace drm_vts {
void VendorModules::scanModules(const std::string &directory) {
DIR* dir = opendir(directory.c_str());
if (dir == NULL) {
ALOGE("Unable to open drm VTS vendor directory %s", directory.c_str());
} else {
struct dirent* entry;
while ((entry = readdir(dir))) {
ALOGD("checking file %s", entry->d_name);
string fullpath = directory + "/" + entry->d_name;
if (endsWith(fullpath, ".so")) {
mPathList.push_back(fullpath);
}
}
closedir(dir);
}
}
DrmHalVTSVendorModule* VendorModules::getModule(const string& path) {
if (mOpenLibraries.find(path) == mOpenLibraries.end()) {
auto library = std::make_unique<SharedLibrary>(String8(path.c_str()));
if (!library) {
ALOGE("failed to map shared library %s", path.c_str());
return NULL;
}
mOpenLibraries[path] = std::move(library);
}
const unique_ptr<SharedLibrary>& library = mOpenLibraries[path];
void* symbol = library->lookup("vendorModuleFactory");
if (symbol == NULL) {
ALOGE("getVendorModule failed to lookup 'vendorModuleFactory' in %s: "
"%s", path.c_str(), library->lastError());
return NULL;
}
typedef DrmHalVTSVendorModule* (*ModuleFactory)();
ModuleFactory moduleFactory = reinterpret_cast<ModuleFactory>(symbol);
return (*moduleFactory)();
}
DrmHalVTSVendorModule* VendorModules::getModuleByName(const string& name) {
for (const auto &path : mPathList) {
auto module = getModule(path);
if (module->getServiceName() == name) {
return module;
}
}
return NULL;
}
};

View file

@ -1,78 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef VENDOR_MODULES_H
#define VENDOR_MODULES_H
#include <map>
#include <vector>
#include <string>
#include <SharedLibrary.h>
using ::android::hardware::drm::V1_0::helper::SharedLibrary;
class DrmHalVTSVendorModule;
namespace drm_vts {
class VendorModules {
public:
/**
* Initialize with a file system path where the shared libraries
* are to be found.
*/
explicit VendorModules(const std::string& dir) {
scanModules(dir);
}
~VendorModules() {}
/**
* Retrieve a DrmHalVTSVendorModule given its full path. The
* getAPIVersion method can be used to determine the versioned
* subclass type.
*/
DrmHalVTSVendorModule* getModule(const std::string& path);
/**
* Retrieve a DrmHalVTSVendorModule given a service name.
*/
DrmHalVTSVendorModule* getModuleByName(const std::string& name);
/**
* Return the list of paths to available vendor modules.
*/
std::vector<std::string> getPathList() const {return mPathList;}
private:
std::vector<std::string> mPathList;
std::map<std::string, std::unique_ptr<SharedLibrary>> mOpenLibraries;
/**
* Scan the list of paths to available vendor modules.
*/
void scanModules(const std::string& dir);
inline bool endsWith(const std::string& str, const std::string& suffix) const {
if (suffix.size() > str.size()) return false;
return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
}
VendorModules(const VendorModules&) = delete;
void operator=(const VendorModules&) = delete;
};
};
#endif // VENDOR_MODULES_H