diff --git a/.gitignore b/.gitignore index 0d9522c..5338e72 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ schemas venv *~ test/* +tests/build/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..bca6706 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.14) + +project(cbexigen VERSION 1) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(GTest) + +add_subdirectory(cbv2g) + +file(GLOB_RECURSE CBV2G_TEST_SOURCES + ./unit/sap/*.cpp + ./unit/iso20/common/*.cpp + ./unit/iso2/*.cpp +) + +add_executable(test-${PROJECT_NAME} + ${CBV2G_TEST_SOURCES} +) + +target_compile_options(test-${PROJECT_NAME} PRIVATE -Wall -Werror -Wshadow) + +target_include_directories(test-${PROJECT_NAME} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(test-${PROJECT_NAME} +PRIVATE + gtest + gtest_main + gmock + pthread + cbv2g +) + +include(GoogleTest) +gtest_discover_tests(test-${PROJECT_NAME} + TEST_PREFIX ${PROJECT_NAME} + TEST_LIST ${PROJECT_NAME}Tests +) + +set_tests_properties(${${PROJECT_NAME}Tests} PROPERTIES TIMEOUT 10) diff --git a/tests/cbv2g/CMakeLists.txt b/tests/cbv2g/CMakeLists.txt new file mode 100644 index 0000000..e0bee5d --- /dev/null +++ b/tests/cbv2g/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(CBV2G_SOURCE_PATH "../../src/output/c") + +file(GLOB_RECURSE CBV2G_SOURCES + ${CBV2G_SOURCE_PATH}/common/*.c + ${CBV2G_SOURCE_PATH}/appHandshake/*.c + ${CBV2G_SOURCE_PATH}/din/*.c + ${CBV2G_SOURCE_PATH}/iso-2/*.c + ${CBV2G_SOURCE_PATH}/iso-20/*.c + ${CBV2G_SOURCE_PATH}/v2gtp/*.c +) + +add_library(cbv2g STATIC + ${CBV2G_SOURCES} +) + +target_compile_options(cbv2g PRIVATE -Werror -Wall -Wextra) + +target_include_directories(cbv2g + PUBLIC + ${CBV2G_SOURCE_PATH}/ + ${CBV2G_SOURCE_PATH}/appHandshake + ${CBV2G_SOURCE_PATH}/common + ${CBV2G_SOURCE_PATH}/din + ${CBV2G_SOURCE_PATH}/iso-2 + ${CBV2G_SOURCE_PATH}/iso-20 + ${CBV2G_SOURCE_PATH}/v2gtp +) diff --git a/tests/unit/iso20/common/test_sessionSetup.cpp b/tests/unit/iso20/common/test_sessionSetup.cpp new file mode 100644 index 0000000..7459c1b --- /dev/null +++ b/tests/unit/iso20/common/test_sessionSetup.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include +#include +#include + +#include "../test_header_utils.hpp" + +class Test_SessionSetup : public testing::Test { +protected: + uint8_t data[256] = {0}; + exi_bitstream_t stream; +}; + +/** \param xml_input (that was used to generate the EXI binary using EXIficient) + * + * + * + * 3030303030303030 + * 1707896956850052 + * + * PIXV12345678901231 + * + */ +TEST_F(Test_SessionSetup, WhenEncodingKnownSessionSetupRequest_ThenResultMatchesExpected) { + static constexpr uint8_t expected[] = + "\x01\xFE\x80\x02\x00\x00\x00\x28" //<- header + "\x80\x8c\x04\x18\x18\x18\x18\x18\x18\x18\x18\x08\x49\xfb\x4f\xba\xba\xa8\x40\x32\x0a\x28\x24\xac\x2b\x18\x99" + "\x19\x9a\x1a\x9b\x1b\x9c\x1c\x98\x18\x99\x19\x98\x80"; + static constexpr size_t streamLen = 0x28; + exi_bitstream_init(&stream, data, sizeof(data), 8, NULL); + iso20_exiDocument exiDoc = {}; + exiDoc.SessionSetupReq_isUsed = 1; + uint8_t sessionID[] = "\x30\x30\x30\x30\x30\x30\x30\x30"; + setHeader(exiDoc.SessionSetupReq.Header, sessionID, 1707896956850052); + setString(exiDoc.SessionSetupReq.EVCCID, "PIXV12345678901231"); + + int res = encode_iso20_exiDocument(&stream, &exiDoc); + size_t len = exi_bitstream_get_length(&stream); + V2GTP20_WriteHeader(&data[0], len, V2GTP20_MAINSTREAM_PAYLOAD_ID); + + ASSERT_EQ(res, 0); + ASSERT_EQ(len, streamLen); + ASSERT_EQ(memcmp(data, expected, sizeof(expected) - 1), 0) + << std::string("\\x") << toHexStr(data, data + sizeof(expected) - 1, "\\x") << '\n' + << std::string("\\x") << toHexStr(&expected[0], &expected[0] + sizeof(expected) - 1, "\\x"); +} + +TEST_F(Test_SessionSetup, WhenDecodingKnownSessionSetupRequest_ThenResultMatchesExpected) { + uint8_t input[] = + "\x01\xFE\x80\x02\x00\x00\x00\x28" //<- header + "\x80\x8c\x04\x18\x18\x18\x18\x18\x18\x18\x18\x08\x49\xfb\x4f\xba\xba\xa8\x40\x32\x0a\x28\x24\xac\x2b\x18\x99" + "\x19\x9a\x1a\x9b\x1b\x9c\x1c\x98\x18\x99\x19\x98\x80"; + iso20_exiDocument exiDoc = {}; + exi_bitstream_init(&stream, &input[0], sizeof(input), 8, NULL); + uint32_t len = 0; + + int res = V2GTP20_ReadHeader(&input[0], &len, V2GTP20_MAINSTREAM_PAYLOAD_ID); + ASSERT_EQ(res, 0); + res = decode_iso20_exiDocument(&stream, &exiDoc); + ASSERT_EQ(res, 0); + + static constexpr size_t streamLen = 0x28; + ASSERT_EQ(len, streamLen); + ASSERT_EQ((int)exiDoc.SessionSetupReq_isUsed, 1); + static const uint8_t sessionID[] = "\x30\x30\x30\x30\x30\x30\x30\x30"; + ASSERT_ISO20_HEADER_EQ(exiDoc.SessionSetupReq.Header, sessionID, 1707896956850052); + ASSERT_ISO20_STREQ(exiDoc.SessionSetupReq.EVCCID, "PIXV12345678901231"); +} diff --git a/tests/unit/iso20/test_header_utils.hpp b/tests/unit/iso20/test_header_utils.hpp new file mode 100644 index 0000000..a9ce270 --- /dev/null +++ b/tests/unit/iso20/test_header_utils.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +template +inline void setHeader(HeaderT& header, const uint8_t (&sessionID)[Len], uint64_t timeStamp, + const std::optional& signature = std::nullopt) { + static_assert(Len >= 8, "SessionID must be size 8. Larger is allowed for string literal \\0 term"); + header.SessionID.bytesLen = 8; + std::copy(&sessionID[0], &sessionID[0] + 8, header.SessionID.bytes); + header.TimeStamp = timeStamp; + header.Signature_isUsed = false; + if (signature) { + throw std::runtime_error("not supported yet"); + } +} +template +inline bool assertHeader(const HeaderT& header, const uint8_t* sessionID, uint64_t timeStamp) { + bool success = false; + [&]() { + ASSERT_EQ(header.SessionID.bytesLen, 8); + ASSERT_EQ(memcmp(header.SessionID.bytes, &sessionID[0], 8), 0); + ASSERT_EQ((int)header.Signature_isUsed, 0); + ASSERT_EQ(header.TimeStamp, timeStamp); + success = true; + }(); + return success; +} +#define ASSERT_ISO20_HEADER_EQ(...) \ + do { /* NOLINT */ \ + if (!assertHeader(__VA_ARGS__)) { \ + return; \ + } \ + } while (0) /* NOLINT */ +#define ASSERT_ISO20_STREQ(str, sv) \ + do { /* NOLINT */ \ + std::string_view svv = sv; \ + ASSERT_EQ((str).charactersLen, svv.size()); \ + ASSERT_EQ((str).characters, svv); \ + } while (0) /* NOLINT */ + +template +inline void setString(StrT& out, std::string_view in) { + out.charactersLen = in.size(); + std::copy(in.begin(), in.end(), out.characters); +} + +template +inline void setBytes(StrT& out, std::span in) { + out.bytesLen = in.size(); + std::copy(in.begin(), in.end(), out.bytes); +} + +template +std::string toHexStr(It begin, It end, std::string_view delimit = "\\x") { + std::stringstream ss; + auto it = begin; + for (; it != end; ++it) { + ss << delimit << std::setfill('0') << std::setw(2) << std::hex << (uint16_t)*it; + } + return std::move(ss).str(); +} diff --git a/tests/unit/sap/test_supportedAppProtocol.cpp b/tests/unit/sap/test_supportedAppProtocol.cpp new file mode 100644 index 0000000..7a5295a --- /dev/null +++ b/tests/unit/sap/test_supportedAppProtocol.cpp @@ -0,0 +1,176 @@ +#include +#include + +#include +#include +#include + +class Test_SupportedAppProtocol : public testing::Test { +protected: + uint8_t data[256] = {0}; + exi_bitstream_t stream; +}; + +static void setProtocol(appHand_AppProtocolType& protoc, std::string_view ns, uint32_t vMaj, uint32_t vMin, + uint8_t schemaID, uint8_t priority) { + protoc.ProtocolNamespace.charactersLen = ns.size(); + std::copy(ns.begin(), ns.end(), protoc.ProtocolNamespace.characters); + protoc.VersionNumberMajor = vMaj; + protoc.VersionNumberMinor = vMin; + protoc.SchemaID = schemaID; + protoc.Priority = priority; +} + +/** \param xml input: + * + * + * urn:iso:15118:2:2010:MsgDef + * 1 + * 0 + * 1 + * 1 + * + * + * urn:din:70121:2012:MsgDef + * 1 + * 0 + * 2 + * 2 + * + * + * urn:iso:std:iso:15118:-20:DC + * 1 + * 0 + * 3 + * 3 + * + * + */ +TEST_F(Test_SupportedAppProtocol, WhenEncodingKnownSupportedAppProtocolRequest_ThenResultMatchesExpected) { + static constexpr uint8_t expected[] = + "\x01\xFE\x80\x01\x00\x00\x00\x66" //<-header + "\x80\x00\xeb\xab\x93\x71\xd3\x4b\x9b\x79\xd1\x89\xa9\x89\x89\xc1\xd1\x91\xd1\x91\x81\x89\x81\xd2\x6b\x9b\x3a" + "\x23\x2b\x30\x01\x00\x00\x04\x00\x01\xb7\x57\x26\xe3\xa6\x46\x96\xe3\xa3\x73\x03\x13\x23\x13\xa3\x23\x03\x13" + "\x23\xa4\xd7\x36\x74\x46\x56\x60\x02\x00\x00\x10\x08\x03\xce\xae\x4d\xc7\x4d\x2e\x6d\xe7\x4e\x6e\x8c\x87\x4d" + "\x2e\x6d\xe7\x46\x26\xa6\x26\x27\x07\x45\xa6\x46\x07\x48\x88\x60\x04\x00\x00\x30\x21"; + static constexpr size_t streamLen = 0x66; + static constexpr std::string_view namespaceISO15118_2 = "urn:iso:15118:2:2010:MsgDef"; + static constexpr std::string_view namespaceDin70121 = "urn:din:70121:2012:MsgDef"; + static constexpr std::string_view namespaceISO15118_20 = "urn:iso:std:iso:15118:-20:DC"; + exi_bitstream_init(&stream, data, sizeof(data), 8, NULL); + appHand_exiDocument exiDoc = {}; + exiDoc.supportedAppProtocolReq_isUsed = 1; + exiDoc.supportedAppProtocolReq.AppProtocol.arrayLen = 3; + setProtocol(exiDoc.supportedAppProtocolReq.AppProtocol.array[0], namespaceISO15118_2, 1, 0, 1, 1); + setProtocol(exiDoc.supportedAppProtocolReq.AppProtocol.array[1], namespaceDin70121, 1, 0, 2, 2); + setProtocol(exiDoc.supportedAppProtocolReq.AppProtocol.array[2], namespaceISO15118_20, 1, 0, 3, 3); + + int res = encode_appHand_exiDocument(&stream, &exiDoc); + int len = exi_bitstream_get_length(&stream); + V2GTP_WriteHeader(data, len); + + ASSERT_EQ(res, 0); + ASSERT_EQ(len, streamLen); + ASSERT_EQ(memcmp(data, expected, sizeof(expected) - 1), 0); +} + +TEST_F(Test_SupportedAppProtocol, WhenDecodingKnownSupportedAppProtocolRequestStream_ThenResultMatchesExpected) { + uint8_t input[] = + "\x01\xFE\x80\x01\x00\x00\x00\x66" //<-header + "\x80\x00\xeb\xab\x93\x71\xd3\x4b\x9b\x79\xd1\x89\xa9\x89\x89\xc1\xd1\x91\xd1\x91\x81\x89\x81\xd2\x6b\x9b\x3a" + "\x23\x2b\x30\x01\x00\x00\x04\x00\x01\xb7\x57\x26\xe3\xa6\x46\x96\xe3\xa3\x73\x03\x13\x23\x13\xa3\x23\x03\x13" + "\x23\xa4\xd7\x36\x74\x46\x56\x60\x02\x00\x00\x10\x08\x03\xce\xae\x4d\xc7\x4d\x2e\x6d\xe7\x4e\x6e\x8c\x87\x4d" + "\x2e\x6d\xe7\x46\x26\xa6\x26\x27\x07\x45\xa6\x46\x07\x48\x88\x60\x04\x00\x00\x30\x21"; + exi_bitstream_init(&stream, input, sizeof(input), 8, NULL); + appHand_exiDocument exiDoc = {}; + uint32_t len = 0; + + int res = V2GTP_ReadHeader(input, &len); + ASSERT_EQ(res, 0); + res = decode_appHand_exiDocument(&stream, &exiDoc); + ASSERT_EQ(res, 0); + + static constexpr size_t streamLen = 0x66; + static constexpr std::string_view namespaceISO15118_2 = "urn:iso:15118:2:2010:MsgDef"; + static constexpr std::string_view namespaceDin70121 = "urn:din:70121:2012:MsgDef"; + static constexpr std::string_view namespaceISO15118_20 = "urn:iso:std:iso:15118:-20:DC"; + ASSERT_EQ(len, streamLen); + ASSERT_EQ((int)exiDoc.supportedAppProtocolReq_isUsed, 1); + ASSERT_EQ(exiDoc.supportedAppProtocolReq.AppProtocol.arrayLen, 3); + + auto& protoc0 = exiDoc.supportedAppProtocolReq.AppProtocol.array[0]; + ASSERT_EQ(protoc0.ProtocolNamespace.charactersLen, namespaceISO15118_2.size()); + ASSERT_EQ(memcmp(protoc0.ProtocolNamespace.characters, namespaceISO15118_2.data(), namespaceISO15118_2.size()), 0); + ASSERT_EQ(protoc0.VersionNumberMajor, 1); + ASSERT_EQ(protoc0.VersionNumberMinor, 0); + ASSERT_EQ(protoc0.SchemaID, 1); + ASSERT_EQ(protoc0.Priority, 1); + + auto& protoc1 = exiDoc.supportedAppProtocolReq.AppProtocol.array[1]; + ASSERT_EQ(protoc1.ProtocolNamespace.charactersLen, namespaceDin70121.size()); + ASSERT_EQ(memcmp(protoc1.ProtocolNamespace.characters, namespaceDin70121.data(), namespaceDin70121.size()), 0); + ASSERT_EQ(protoc1.VersionNumberMajor, 1); + ASSERT_EQ(protoc1.VersionNumberMinor, 0); + ASSERT_EQ(protoc1.SchemaID, 2); + ASSERT_EQ(protoc1.Priority, 2); + + auto& protoc2 = exiDoc.supportedAppProtocolReq.AppProtocol.array[2]; + ASSERT_EQ(protoc2.ProtocolNamespace.charactersLen, namespaceISO15118_20.size()); + ASSERT_EQ(memcmp(protoc2.ProtocolNamespace.characters, namespaceISO15118_20.data(), namespaceISO15118_20.size()), + 0); + ASSERT_EQ(protoc2.VersionNumberMajor, 1); + ASSERT_EQ(protoc2.VersionNumberMinor, 0); + ASSERT_EQ(protoc2.SchemaID, 3); + ASSERT_EQ(protoc2.Priority, 3); +} + +/** \param xml input: + * + * + * OK_SuccessfulNegotiation + * 1 + * + */ +TEST_F(Test_SupportedAppProtocol, WhenEncodingKnownSupportedAppProtocolResponse_ThenResultMatchesExpected) { + static constexpr uint8_t expected[] = + "\x01\xfe\x80\x01\x00\x00\x00\x04"//<-header + "\x80\x40\x00\x40"; + + static constexpr size_t streamLen = 0x04; + exi_bitstream_init(&stream, data, sizeof(data), 8, NULL); + appHand_exiDocument exiDoc = {}; + exiDoc.supportedAppProtocolRes_isUsed = 1; + exiDoc.supportedAppProtocolRes.SchemaID_isUsed = 1; + exiDoc.supportedAppProtocolRes.SchemaID = 1; + exiDoc.supportedAppProtocolRes.ResponseCode = appHand_responseCodeType_OK_SuccessfulNegotiation; + + int res = encode_appHand_exiDocument(&stream, &exiDoc); + ASSERT_EQ(res, 0); + int len = exi_bitstream_get_length(&stream); + V2GTP_WriteHeader(data, len); + + ASSERT_EQ(len, streamLen); + ASSERT_EQ(memcmp(data, expected, sizeof(expected) - 1), 0); +} +TEST_F(Test_SupportedAppProtocol, WhenDecodingKnownSupportedAppProtocolResponseStream_ThenResultMatchesExpected) { + uint8_t input[] = + "\x01\xfe\x80\x01\x00\x00\x00\x04"//<-header + "\x80\x40\x00\x40"; + exi_bitstream_init(&stream, input, sizeof(input), 8, NULL); + appHand_exiDocument exiDoc = {}; + uint32_t len = 0; + + V2GTP_ReadHeader(input, &len); + decode_appHand_exiDocument(&stream, &exiDoc); + + static constexpr size_t streamLen = 0x04; + ASSERT_EQ(len, streamLen); + ASSERT_EQ((int)exiDoc.supportedAppProtocolRes_isUsed, 1); + ASSERT_EQ(exiDoc.supportedAppProtocolRes.ResponseCode, appHand_responseCodeType_OK_SuccessfulNegotiation); + ASSERT_EQ((int)exiDoc.supportedAppProtocolRes.SchemaID_isUsed, 1); + ASSERT_EQ(exiDoc.supportedAppProtocolRes.SchemaID, 1); +}