22#include <gtest/gtest.h>
46[[nodiscard]]
Bytes BuildBlockHeader(
47 std::uint16_t blockType,
48 std::uint16_t blockLength,
49 std::uint8_t versionHigh = 1U,
50 std::uint8_t versionLow = 2U)
55 data.push_back(versionHigh);
56 data.push_back(versionLow);
67[[nodiscard]]
Bytes BuildMaintenanceItem(
68 std::uint16_t blockType,
69 std::uint16_t blockLength,
70 std::uint32_t maintenanceStatus)
75 const Bytes header = BuildBlockHeader(blockType, blockLength);
76 item.insert(item.end(), header.begin(), header.end());
95[[nodiscard]]
Bytes BuildUploadRetrievalItem(
97 std::uint16_t blockType,
98 std::uint16_t blockLength,
99 std::uint32_t recordIndex,
100 std::uint32_t recordLength)
105 const Bytes header = BuildBlockHeader(blockType, blockLength);
106 item.insert(item.end(), header.begin(), header.end());
108 item.push_back(0x00);
109 item.push_back(0x00);
126[[nodiscard]]
Bytes BuildPeAlarmItem(
127 std::uint16_t blockType,
128 std::uint16_t blockLength,
129 std::uint8_t versionHigh,
130 std::uint8_t versionLow,
131 std::uint8_t operationalMode)
136 const Bytes header = BuildBlockHeader(blockType, blockLength, versionHigh, versionLow);
137 item.insert(item.end(), header.begin(), header.end());
138 item.push_back(operationalMode);
149[[nodiscard]]
Bytes BuildRsAlarmItem(
Usi usi, std::uint16_t alarmInfo)
166[[nodiscard]]
Bytes BuildPralAlarmItem(
167 std::uint16_t channelNumber,
168 std::uint16_t channelProperties,
169 std::uint16_t reason,
170 std::uint16_t extendedReason,
171 const Bytes& additionalValue = {})
179 item.insert(item.end(), additionalValue.begin(), additionalValue.end());
191[[nodiscard]]
Bytes BuildAlarmNotification(
192 std::uint16_t blockType,
193 std::uint16_t specifier,
194 const Bytes& items = {},
195 std::uint16_t blockLengthOverride = 0)
207 EXPECT_EQ(body.size(), 20U);
209 if (body.size() < 20U)
214 Bytes bodyWithItems{0x02U, 0x01U};
215 bodyWithItems.insert(bodyWithItems.end(), body.begin(), body.end());
216 bodyWithItems.insert(bodyWithItems.end(), items.begin(), items.end());
218 const auto actualBlockLength =
static_cast<std::uint16_t
>(bodyWithItems.size());
219 const auto blockLength =
220 blockLengthOverride == 0U ? actualBlockLength : blockLengthOverride;
225 result.insert(result.end(), bodyWithItems.begin(), bodyWithItems.end());
237[[nodiscard]]
Bytes BuildChannelDiagnosisItem(
238 std::uint16_t channelNumber,
239 std::uint16_t channelProperties,
240 std::uint16_t channelErrorType)
260[[nodiscard]]
Bytes BuildQualifiedDiagnosisItem(
261 std::uint16_t channelNumber,
262 std::uint16_t channelProperties,
263 std::uint16_t channelErrorType,
264 std::uint16_t extendedErrorType,
265 std::uint32_t extendedAdditionalValue,
266 std::uint32_t qualifier)
281class DiagnosisItemTests :
public ::testing::Test
287class MaintenanceItemTests :
public ::testing::Test
293class UploadRetrievalItemTests :
public ::testing::Test
299class RsAlarmItemTests :
public ::testing::Test
305class PralAlarmItemTests :
public ::testing::Test
311class PeAlarmItemTests :
public ::testing::Test
317class AlarmItemVariantTests :
public ::testing::Test
323class AlarmNotificationTests :
public ::testing::Test
328TEST_F(DiagnosisItemTests, ChannelNumberWithoutAccumulativeBit)
331 item.channelNumber = 0x1234U;
333 EXPECT_EQ(item.ChannelNumberValue(), 0x1234U);
334 EXPECT_FALSE(item.IsAccumulative());
338TEST_F(DiagnosisItemTests, ChannelTypeUsesLowByte)
341 item.channelProperties = 0xABCDU;
343 EXPECT_EQ(item.ChannelTypeValue(),
static_cast<std::uint8_t
>(0xCDU));
347TEST_F(DiagnosisItemTests, QualifiedDiagnosisIsExtendedAndQualified)
352 EXPECT_TRUE(item.IsExtended());
353 EXPECT_TRUE(item.IsQualified());
357TEST_F(DiagnosisItemTests, ExtendedDiagnosisIsExtendedButNotQualified)
362 EXPECT_TRUE(item.IsExtended());
363 EXPECT_FALSE(item.IsQualified());
367TEST_F(DiagnosisItemTests, StandardDiagnosisIsNeitherExtendedNorQualified)
372 EXPECT_FALSE(item.IsExtended());
373 EXPECT_FALSE(item.IsQualified());
377TEST_F(DiagnosisItemTests, ParseQualifiedChannelDiagnosis)
379 const Bytes data = BuildQualifiedDiagnosisItem(
389 EXPECT_EQ(offset, data.size());
391 const auto* item = std::get_if<DiagnosisItem>(&variant);
392 ASSERT_NE(item,
nullptr);
395 EXPECT_EQ(item->channelNumber, 0x8005U);
396 EXPECT_EQ(item->ChannelNumberValue(), 5U);
397 EXPECT_TRUE(item->IsAccumulative());
398 EXPECT_EQ(item->channelProperties, 0xA5C3U);
399 EXPECT_EQ(item->ChannelTypeValue(), 0xC3U);
400 EXPECT_EQ(item->channelErrorType, 0x1122U);
401 EXPECT_EQ(item->extChannelErrorType, 0x3344U);
402 EXPECT_EQ(item->extChannelAddValue, 0x55667788U);
403 EXPECT_EQ(item->qualifiedChannelQualifier, 0x99AABBCCU);
404 EXPECT_TRUE(item->IsExtended());
405 EXPECT_TRUE(item->IsQualified());
409TEST_F(DiagnosisItemTests, RejectsTruncatedExtendedDiagnosis)
423TEST_F(DiagnosisItemTests, RejectsTruncatedQualifiedQualifier)
425 Bytes data = BuildQualifiedDiagnosisItem(
433 ASSERT_GT(data.size(), 0U);
440TEST_F(MaintenanceItemTests, MaintenanceStatusFlagsAreIndependent)
442 MaintenanceItem item;
444 item.maintenanceStatus = 0U;
445 EXPECT_FALSE(item.MaintenanceRequired());
446 EXPECT_FALSE(item.MaintenanceDemanded());
448 item.maintenanceStatus = 0x01U;
449 EXPECT_TRUE(item.MaintenanceRequired());
450 EXPECT_FALSE(item.MaintenanceDemanded());
452 item.maintenanceStatus = 0x02U;
453 EXPECT_FALSE(item.MaintenanceRequired());
454 EXPECT_TRUE(item.MaintenanceDemanded());
456 item.maintenanceStatus = 0x03U;
457 EXPECT_TRUE(item.MaintenanceRequired());
458 EXPECT_TRUE(item.MaintenanceDemanded());
462TEST_F(MaintenanceItemTests, ParseMaintenanceItem)
464 constexpr std::uint16_t blockType = 0x8101U;
465 constexpr std::uint16_t blockLength = 0x0010U;
466 constexpr std::uint32_t maintenanceStatus = 0xA5A5A5A5U;
468 const Bytes data = BuildMaintenanceItem(blockType, blockLength, maintenanceStatus);
472 EXPECT_EQ(offset, data.size());
474 const auto* item = std::get_if<MaintenanceItem>(&variant);
475 ASSERT_NE(item,
nullptr);
478 EXPECT_EQ(
static_cast<std::uint16_t
>(item->blockType), blockType);
479 EXPECT_EQ(item->blockLength, blockLength);
480 EXPECT_EQ(item->blockVersion, 0x0102U);
481 EXPECT_EQ(item->maintenanceStatus, maintenanceStatus);
482 EXPECT_TRUE(item->MaintenanceRequired());
483 EXPECT_FALSE(item->MaintenanceDemanded());
488TEST_F(MaintenanceItemTests, RejectsTruncatedMaintenanceItem)
493 const Bytes header = BuildBlockHeader(0x8101U, 0x0010U);
494 data.insert(data.end(), header.begin(), header.end());
500TEST_F(UploadRetrievalItemTests, ParseUploadItem)
502 const Bytes data = BuildUploadRetrievalItem(
511 EXPECT_EQ(offset, data.size());
513 const auto* item = std::get_if<UploadRetrievalItem>(&variant);
514 ASSERT_NE(item,
nullptr);
517 EXPECT_EQ(
static_cast<std::uint16_t
>(item->blockType), 0x8201U);
518 EXPECT_EQ(item->blockLength, 0x0020U);
519 EXPECT_EQ(item->blockVersion, 0x0102U);
520 EXPECT_EQ(item->urRecordIndex, 0x01020304U);
521 EXPECT_EQ(item->urRecordLength, 0xA1B2C3D4U);
522 EXPECT_TRUE(item->IsUpload());
523 EXPECT_FALSE(item->IsRetrieval());
527TEST_F(UploadRetrievalItemTests, ParseIParameterItemAsRetrieval)
529 const Bytes data = BuildUploadRetrievalItem(
538 EXPECT_EQ(offset, data.size());
540 const auto* item = std::get_if<UploadRetrievalItem>(&variant);
541 ASSERT_NE(item,
nullptr);
544 EXPECT_EQ(item->urRecordIndex, 0x11223344U);
545 EXPECT_EQ(item->urRecordLength, 0x55667788U);
546 EXPECT_FALSE(item->IsUpload());
547 EXPECT_TRUE(item->IsRetrieval());
551TEST_F(UploadRetrievalItemTests, RejectsTruncatedUploadRetrievalItem)
556 const Bytes header = BuildBlockHeader(0x8201U, 0x0020U);
557 data.insert(data.end(), header.begin(), header.end());
563TEST_F(RsAlarmItemTests, DISABLED_ParsesAllRsAlarmVariants)
565 const std::vector<Usi> usis{
571 for (
const Usi usi : usis)
573 SCOPED_TRACE(
static_cast<std::uint16_t
>(usi));
575 const Bytes data = BuildRsAlarmItem(usi, 0xF8A5U);
579 EXPECT_EQ(offset, data.size());
581 const auto* item = std::get_if<RSAlarmItem>(&variant);
582 ASSERT_NE(item,
nullptr);
584 EXPECT_EQ(item->userStructureId, usi);
585 EXPECT_EQ(item->rsAlarmInfo, 0xF8A5U);
586 EXPECT_EQ(item->RsSpecifier(), 0x08A5U);
587 EXPECT_EQ(item->RsSequenceNumber(), 0x08A5U);
593TEST_F(RsAlarmItemTests, RejectsTruncatedRsAlarm)
597 data.push_back(0x12U);
603TEST_F(PralAlarmItemTests, ParsePralAlarm)
605 const Bytes additional{0xDEU, 0xADU, 0xBEU, 0xEFU};
607 const Bytes data = BuildPralAlarmItem(
616 EXPECT_EQ(offset, data.size());
618 const auto* item = std::get_if<PRALAlarmItem>(&variant);
619 ASSERT_NE(item,
nullptr);
622 EXPECT_EQ(item->channelNumber, 0x8005U);
623 EXPECT_EQ(item->propAlChannelProperties, 0x1234U);
624 EXPECT_EQ(item->prAlReason, 0x5678U);
625 EXPECT_EQ(item->prAlExtReason, 0x9ABCU);
626 EXPECT_EQ(item->prAlReasonAddValue, additional);
631TEST_F(PralAlarmItemTests, ParsePralAlarmWithoutAdditionalData)
633 const Bytes data = BuildPralAlarmItem(
641 EXPECT_EQ(offset, data.size());
643 const auto* item = std::get_if<PRALAlarmItem>(&variant);
644 ASSERT_NE(item,
nullptr);
646 EXPECT_TRUE(item->prAlReasonAddValue.empty());
650TEST_F(PralAlarmItemTests, RejectsTruncatedPralAlarm)
663TEST_F(PeAlarmItemTests, ParsePeAlarmFields)
665 const Bytes data = BuildPeAlarmItem(
674 EXPECT_EQ(offset, data.size());
676 const auto* item = std::get_if<PEAlarmItem>(&variant);
677 ASSERT_NE(item,
nullptr);
680 EXPECT_EQ(
static_cast<std::uint16_t
>(item->blockType), 0x8311U);
681 EXPECT_EQ(item->blockLength, 0x0042U);
682 EXPECT_EQ(item->blockVersion, 0x0304U);
683 EXPECT_EQ(item->peOperationalMode, 0x7FU);
689TEST_F(PeAlarmItemTests, RejectsTruncatedPeAlarm)
694 const Bytes header = BuildBlockHeader(0x8311U, 0x0003U);
695 data.insert(data.end(), header.begin(), header.end());
701TEST_F(AlarmItemVariantTests, VariantHelpersCoverAllAlarmItemTypes)
706 DiagnosisItem diagnosis;
709 MaintenanceItem maintenance;
712 UploadRetrievalItem upload;
724 const std::vector<AlarmItemVariant> items{
743TEST_F(AlarmItemVariantTests, IParameterItemUsiName)
752TEST_F(AlarmItemVariantTests, ConcreteItemsExposeUsiNames)
757 DiagnosisItem diagnosis;
760 MaintenanceItem maintenance;
761 UploadRetrievalItem upload;
777TEST_F(AlarmItemVariantTests, ParseAlarmItemHonorsOffset)
779 Bytes data{0xAAU, 0xBBU, 0xCCU};
781 const Bytes item = BuildChannelDiagnosisItem(
786 data.insert(data.end(), item.begin(), item.end());
790 EXPECT_EQ(offset, data.size());
792 const auto* diagnosis = std::get_if<DiagnosisItem>(&variant);
793 ASSERT_NE(diagnosis,
nullptr);
795 EXPECT_EQ(diagnosis->channelNumber, 0x8001U);
796 EXPECT_EQ(diagnosis->channelProperties, 0x1234U);
797 EXPECT_EQ(diagnosis->channelErrorType, 0x5678U);
801TEST_F(AlarmItemVariantTests, ParseGenericAlarmWithOffset)
803 Bytes data{0x00U, 0x01U, 0x02U};
806 data.push_back(0xDEU);
807 data.push_back(0xADU);
811 EXPECT_EQ(offset, data.size());
813 const auto*
generic = std::get_if<AlarmItem>(&variant);
814 ASSERT_NE(generic,
nullptr);
816 EXPECT_EQ(generic->userStructureId,
static_cast<Usi>(0x1234U));
817 EXPECT_EQ(generic->rawData,
Bytes({0xDEU, 0xADU}));
821TEST_F(AlarmNotificationTests, LowPriorityHelperReturnsTrue)
823 const Bytes data = BuildAlarmNotification(
829 EXPECT_TRUE(notification.IsLowPriority());
830 EXPECT_FALSE(notification.IsHighPriority());
834TEST_F(AlarmNotificationTests, ParsesAllAlarmNotificationFields)
836 constexpr std::uint16_t sequence = 0x0345U;
838 const std::uint16_t specifier =
845 const Bytes data = BuildAlarmNotification(
851 EXPECT_TRUE(notification.IsHighPriority());
852 EXPECT_FALSE(notification.IsLowPriority());
854 EXPECT_EQ(notification.blockVersion.first, 0x02U);
855 EXPECT_EQ(notification.blockVersion.second, 0x01U);
856 EXPECT_EQ(notification.alarmType,
static_cast<AlarmType>(0x0007U));
860 EXPECT_EQ(notification.api, 0x12345678U);
861 EXPECT_EQ(notification.slotNumber, 0x0021U);
862 EXPECT_EQ(notification.subslotNumber, 0x1002U);
863 EXPECT_EQ(notification.moduleIdentNumber, 0x11223344U);
864 EXPECT_EQ(notification.submoduleIdentNumber, 0x55667788U);
866 EXPECT_EQ(notification.alarmSequenceNumber, sequence);
867 EXPECT_TRUE(notification.channelDiagnosis);
868 EXPECT_TRUE(notification.manufacturerSpecific);
869 EXPECT_TRUE(notification.submoduleDiagnosisState);
870 EXPECT_TRUE(notification.arDiagnosisState);
872 EXPECT_EQ(notification.Location(),
"305419896:33:0x1002");
873 EXPECT_TRUE(notification.rawPayload.empty());
874 EXPECT_TRUE(notification.items.empty());
878TEST_F(AlarmNotificationTests, DiagnosisFlagsRemainFalseWhenUnset)
880 const Bytes data = BuildAlarmNotification(
886 EXPECT_FALSE(notification.channelDiagnosis);
887 EXPECT_FALSE(notification.manufacturerSpecific);
888 EXPECT_FALSE(notification.submoduleDiagnosisState);
889 EXPECT_FALSE(notification.arDiagnosisState);
890 EXPECT_EQ(notification.alarmSequenceNumber, 5U);
894TEST_F(AlarmNotificationTests, RawPayloadContainsOnlyBlockBodyAfterFixedHeader)
896 const Bytes item = BuildChannelDiagnosisItem(
901 const Bytes data = BuildAlarmNotification(
908 ASSERT_EQ(notification.items.size(), 1U);
910 ASSERT_EQ(notification.rawPayload.size(), 8U);
911 EXPECT_EQ(
Bytes(notification.rawPayload.begin(), notification.rawPayload.end()),
916TEST_F(AlarmNotificationTests, TruncatedTrailingAlarmItemIsIgnoredAfterValidItems)
918 Bytes items = BuildChannelDiagnosisItem(
923 items.push_back(0x80U);
925 const Bytes data = BuildAlarmNotification(
932 ASSERT_EQ(notification.items.size(), 1U);
934 const auto* diagnosis = std::get_if<DiagnosisItem>(¬ification.items.front());
935 ASSERT_NE(diagnosis,
nullptr);
937 EXPECT_EQ(diagnosis->channelNumber, 2U);
938 EXPECT_EQ(diagnosis->channelProperties, 0x0803U);
939 EXPECT_EQ(diagnosis->channelErrorType, 4U);
943TEST_F(AlarmNotificationTests, RejectsTooSmallBlockLength)
945 const Bytes data = BuildAlarmNotification(
955TEST_F(AlarmNotificationTests, RejectsBlockExtendingPastInput)
957 const Bytes data = BuildAlarmNotification(
963 ASSERT_EQ(data.size(), 26U);
969TEST_F(AlarmNotificationTests, AcceptsExactFixedSizeBlock)
971 const Bytes data = BuildAlarmNotification(
975 ASSERT_EQ(data.size(), 26U);
979 EXPECT_TRUE(notification.items.empty());
980 EXPECT_TRUE(notification.rawPayload.empty());
984TEST_F(AlarmNotificationTests, IgnoresBytesAfterDeclaredBlock)
986 Bytes data = BuildAlarmNotification(
990 data.push_back(0xDEU);
991 data.push_back(0xADU);
992 data.push_back(0xBEU);
993 data.push_back(0xEFU);
997 EXPECT_TRUE(notification.items.empty());
998 EXPECT_TRUE(notification.rawPayload.empty());
999 EXPECT_EQ(data.size(), 30U);
1003TEST_F(AlarmNotificationTests, ValidItemsRemainBoundedByBlockLength)
1005 const Bytes item = BuildChannelDiagnosisItem(
1010 Bytes data = BuildAlarmNotification(
1015 const std::size_t blockSize = data.size();
1017 data.insert(data.end(), 16U, 0x00U);
1021 EXPECT_EQ(notification.items.size(), 1U);
1022 EXPECT_EQ(notification.rawPayload.size(), 8U);
1023 EXPECT_EQ(blockSize, 34U);
1027TEST_F(AlarmNotificationTests, LocationFormatsApiSlotAndSubslot)
1029 AlarmNotification notification;
1030 notification.api = 0xFFFFFFFFU;
1031 notification.slotNumber = 0xFFFFU;
1032 notification.subslotNumber = 0xFFFFU;
1034 EXPECT_EQ(notification.Location(),
"4294967295:65535:0xFFFF");
1038TEST_F(AlarmNotificationTests, RejectsEmptyInput)
1044TEST_F(AlarmItemVariantTests, UsiNameOfMatchesUsiOf)
1046 std::vector<AlarmItemVariant> items;
1049 generic.userStructureId =
static_cast<Usi>(0x7001U);
1050 items.emplace_back(generic);
1052 DiagnosisItem diagnosis;
1054 items.emplace_back(diagnosis);
1056 MaintenanceItem maintenance;
1057 items.emplace_back(maintenance);
1059 UploadRetrievalItem upload;
1061 items.emplace_back(upload);
1064 items.emplace_back(pe);
1068 items.emplace_back(rs);
1071 items.emplace_back(pral);
Declares PROFINET alarm, diagnosis, maintenance, and USI payload types and parsers.
void PutU8(std::vector< std::uint8_t > &out, std::uint8_t value)
Append a single byte to a buffer.
void PutU16(std::vector< std::uint8_t > &out, std::uint16_t value)
Append a 16-bit value to a buffer in big-endian order.
void PutU32(std::vector< std::uint8_t > &out, std::uint32_t value)
Append a 32-bit value to a buffer in big-endian order.
@ AlarmNotificationHigh
Alarm notification high block.
@ AlarmNotificationLow
Alarm notification low block.
TEST_F(AlarmListenerTests, InitializesWithEndpoint)
std::pair< AlarmItemVariant, std::size_t > ParseAlarmItem(const Bytes &data, std::size_t offset=0)
Parse a single alarm item starting at an offset.
AlarmNotification ParseAlarmNotification(const Bytes &data)
Parse a complete AlarmNotification PDU (BlockHeader + body + items).
AlarmType
PROFINET Alarm Types used in AlarmNotification blocks.
std::string GetUsiName(Usi usi)
Get the human-readable name for a User Structure Identifier.
std::string GetPeModeName(std::uint8_t mode)
Get the human-readable name for a PROFIenergy operational mode.
std::string UsiNameOf(const AlarmItemVariant &item)
Get the human-readable USI name for any AlarmItemVariant alternative.
Usi
PROFINET User Structure Identifiers (USI).
@ Maintenance
Maintenance item.
@ IParameter
iParameter item.
@ PralAlarm
PRAL alarm item.
@ ExtendedChannelDiagnosis
Extended channel diagnosis.
@ ChannelDiagnosis
Channel diagnosis.
@ RsAlarmHigh
RS alarm item for high alarm.
@ RsAlarmSubmodule
RS alarm item for submodule.
@ RsAlarmLow
RS alarm item for low alarm.
@ QualifiedChannelDiagnosis
Qualified channel diagnosis.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
std::string GetAlarmTypeName(AlarmType alarmType)
Get the human-readable name for an alarm type.
Usi UsiOf(const AlarmItemVariant &item)
Get the USI shared by every AlarmItemVariant alternative.
std::variant< AlarmItem, DiagnosisItem, MaintenanceItem, UploadRetrievalItem, PEAlarmItem, RSAlarmItem, PRALAlarmItem > AlarmItemVariant
A parsed alarm payload item; the held alternative is selected by USI.
PROFINET protocol wire-format structures and serialization helpers.
Generic helpers for reading and writing integer and byte data in big- and little-endian wire formats.