102using namespace std::chrono_literals;
147 info.
name =
"test-device";
148 info.ip =
"127.0.0.1";
151 info.deviceHigh = 0x1234;
152 info.deviceLow = 0x5678;
153 info.vendorHigh = 0x9ABC;
154 info.vendorLow = 0xDEF0;
220 std::uint32_t argsMaximumStatus,
278 std::vector<BlockView> result;
280 std::size_t offset = 0;
282 while (offset + 4 <= data.size())
287 const std::size_t blockSize =
290 if (blockSize > data.size() - offset)
309 const std::size_t size =
313 data.begin() + block.
offset,
314 data.begin() + block.
offset + size);
319 std::uint16_t blockType)
323 for (
const auto& block : blocks)
325 if (block.blockType == blockType)
336 std::uint16_t blockType)
338 std::size_t count = 0;
342 for (
const auto& block : blocks)
344 if (block.blockType == blockType)
365 std::array<std::uint8_t, uuidLenght>
arUuid{};
374 std::array<std::uint8_t, uuidLenght>
arUuid{};
414 <<
"Failed to open test UDP socket: "
420 asio::ip::udp::endpoint(
426 <<
"Failed to bind test UDP socket: "
517 std::scoped_lock lock(
mutex);
529 std::chrono::milliseconds timeout =
530 std::chrono::milliseconds{1000})
555 const std::array<std::uint8_t, uuidLenght>& arUuid,
556 std::uint16_t sessionKey)
560 throw std::runtime_error(
561 "Cannot send ApplicationReady before CONNECT");
569 throw std::runtime_error(
570 "No CONNECT request has been received");
598 std::chrono::milliseconds timeout =
599 std::chrono::milliseconds{1000})
const
621 [[nodiscard]] std::optional<ControlResponse>
630 [[nodiscard]] std::optional<ControlRequest>
639 [[nodiscard]] std::optional<PNRPCHeader>
648 [[nodiscard]] std::optional<PNRPCHeader>
657 [[nodiscard]] std::optional<PNNRDData>
689 std::uint16_t operation,
691 std::optional<PNRPCHeader> packetTemplate = std::nullopt)
700 request = *packetTemplate;
708 throw std::runtime_error(
709 "Cannot send RPC REQUEST: no packet template is available");
716 static_cast<std::uint16_t
>(
payload.size());
733 std::uint16_t operation,
735 std::optional<PNRPCHeader> packetTemplate = std::nullopt)
744 response = *packetTemplate;
752 throw std::runtime_error(
753 "Cannot send RPC RESPONSE: no packet template is available");
760 static_cast<std::uint16_t
>(
payload.size());
777 std::uint16_t blockType,
778 std::uint16_t controlCommand,
779 const std::array<std::uint8_t, uuidLenght>& arUuid,
780 std::uint16_t sessionKey,
781 std::uint16_t controlBlockProperties = 0)
783 const Bytes controlBlock =
789 controlBlockProperties);
827 asio::ip::udp::endpoint sender;
830 const std::size_t received =
861 const asio::ip::udp::endpoint& sender)
968 const asio::ip::udp::endpoint& remote)
982 [[nodiscard]] asio::ip::udp::endpoint
990 asio::ip::make_address_v4(
"127.0.0.1"),
1005 const asio::ip::udp::endpoint& sender)
1014 const asio::ip::udp::endpoint& sender)
1023 const asio::ip::udp::endpoint& sender)
1045 const asio::ip::udp::endpoint& sender,
1046 bool sendRequestBeforeResponse,
1054 const Bytes nrdPayload =
1057 const bool hasArBlock =
1060 BlockType::ApplicationRelationData);
1072 const bool hasAlarmCr =
1075 BlockType::AlarmCrRequest);
1083 bool sawInput =
false;
1084 bool sawOutput =
false;
1086 std::size_t offset = 0;
1095 const std::size_t blockSize =
1097 static_cast<std::size_t
>(
1101 nrdPayload.size() - offset)
1109 const Bytes blockBytes(
1110 nrdPayload.begin() +
1112 nrdPayload.begin() +
1114 offset + blockSize));
1131 catch (
const std::exception&)
1137 offset += blockSize;
1146 BLOCK_EXPECTED_SUBMODULE_REQ));
1163 if (sendRequestBeforeResponse)
1168 const Bytes unsolicited =
1171 asio::error_code ec;
1174 asio::buffer(unsolicited),
1204 const asio::ip::udp::endpoint& sender)
1211 const Bytes nrdPayload =
1230 Bytes recordData = {
1241 static_cast<std::uint32_t
>(
1247 const Bytes responsePayload =
1250 const Bytes responseNrd =
1271 const asio::ip::udp::endpoint& sender)
1278 const Bytes nrdPayload =
1298 std::scoped_lock lock(
mutex);
1304 const Bytes responseNrd =
1322 const std::array<std::uint8_t, uuidLenght>& arUuid,
1323 std::uint16_t sessionKey)
1336 controlBlock.
arUuid = arUuid;
1343 return controlBlock.
ToBytes();
1346 static std::optional<ControlRequest>
1353 return std::nullopt;
1359 return std::nullopt;
1372 return std::nullopt;
1375 constexpr std::size_t kControlBlockSize = 32;
1380 return std::nullopt;
1389 static_cast<std::uint16_t
>(
1390 (
static_cast<std::uint16_t
>(data[0]) << OneOctetShift) |
1391 static_cast<std::uint16_t
>(data[1]));
1396 return std::nullopt;
1401 std::ranges::copy(data | std::views::drop(8) | std::views::take(
uuidLenght), result.
arUuid.begin());
1404 static_cast<std::uint16_t
>(
1405 (
static_cast<std::uint16_t
>(data[24]) << OneOctetShift) |
1406 static_cast<std::uint16_t
>(data[25]));
1409 static_cast<std::uint16_t
>(
1410 (
static_cast<std::uint16_t
>(data[28]) << OneOctetShift) |
1411 static_cast<std::uint16_t
>(data[29]));
1414 static_cast<std::uint16_t
>(
1415 (
static_cast<std::uint16_t
>(data[30]) << OneOctetShift) |
1416 static_cast<std::uint16_t
>(data[31]));
1421 static std::optional<ControlResponse>
1428 return std::nullopt;
1434 return std::nullopt;
1447 return std::nullopt;
1450 constexpr std::size_t kControlBlockSize = 32;
1455 return std::nullopt;
1464 static_cast<std::uint16_t
>(
1465 (
static_cast<std::uint16_t
>(data[0]) << OneOctetShift) |
1466 static_cast<std::uint16_t
>(data[1]));
1471 return std::nullopt;
1474 std::ranges::copy(data | std::views::drop(8) | std::views::take(
uuidLenght), result.
arUuid.begin());
1477 static_cast<std::uint16_t
>(
1478 (
static_cast<std::uint16_t
>(data[24]) << OneOctetShift) |
1479 static_cast<std::uint16_t
>(data[25]));
1482 static_cast<std::uint16_t
>(
1483 (
static_cast<std::uint16_t
>(data[28]) << OneOctetShift) |
1484 static_cast<std::uint16_t
>(data[29]));
1487 static_cast<std::uint16_t
>(
1488 (
static_cast<std::uint16_t
>(data[30]) << OneOctetShift) |
1489 static_cast<std::uint16_t
>(data[31]));
1524 const asio::ip::udp::endpoint target =
1527 asio::error_code ec;
1530 asio::buffer(bytes),
1537 throw std::system_error(
1539 "Failed to send RPC packet");
1544 std::uint16_t blockType,
1545 std::uint16_t controlCommand,
1546 const std::array<std::uint8_t, uuidLenght>& arUuid,
1547 std::uint16_t sessionKey,
1548 std::uint16_t controlBlockProperties)
1561 controlBlock.
arUuid = arUuid;
1566 controlBlockProperties;
1568 return controlBlock.
ToBytes();
1604 std::optional<PNRPCHeader>
1607 std::optional<PNRPCHeader>
1610 std::optional<ControlRequest>
1613 std::optional<ControlResponse>
1631 slot0.moduleIdent = 0x00008001;
1632 slot0.submoduleIdent = 0x00000000;
1633 slot0.inputLength = 0;
1634 slot0.outputLength = 0;
1639 slot1.moduleIdent = 0x00008000;
1640 slot1.submoduleIdent = 0x00000022;
1641 slot1.inputLength = 34;
1642 slot1.outputLength = 0;
1647 slot2.moduleIdent = 0x00008100;
1648 slot2.submoduleIdent = 0x00000011;
1649 slot2.inputLength = 0;
1650 slot2.outputLength = 16;
1652 setup.slots.push_back(slot0);
1653 setup.slots.push_back(slot1);
1654 setup.slots.push_back(slot2);
1668 std::make_unique<TestRpcDevice>(
1672 std::make_unique<RPCCon>(
1701 std::optional<dcp::DCPDeviceDescription>
info;
1728 EXPECT_FALSE(result.has_value());
1763 EXPECT_THROW(connection.
Connect(), std::invalid_argument);
1964 ASSERT_TRUE(result.has_value());
2028 <<
"Enable once the test device supports CONNECT + READ in one "
2029 "stateful session.";
2039 <<
"Enable once the test device supports CONNECT + WRITE in one "
2040 "stateful session.";
2050 <<
"Enable once the simulated device supports a complete implicit "
2051 "IODReadReq/IODReadRes exchange.";
2107 connection.
Close());
2114 connection.
Close());
2130 auto& io = connection.IoContext();
2134 &connection.IoContext());
2155 setup.
slots[0].slot,
2159 setup.
slots[0].moduleIdent,
2163 setup.
slots[1].slot,
2167 setup.
slots[1].moduleIdent,
2171 setup.
slots[1].submoduleIdent,
2175 setup.
slots[1].inputLength,
2179 setup.
slots[1].outputLength,
2183 setup.
slots[2].slot,
2187 setup.
slots[2].moduleIdent,
2191 setup.
slots[2].submoduleIdent,
2195 setup.
slots[2].inputLength,
2199 setup.
slots[2].outputLength,
2222 connection->IsConnected());
2225 testDevice->WaitForConnect());
2228 testDevice->SendApplicationReadyRequest(
2229 connection->ArUuid(),
2230 connection->SessionKey()));
2233 connection->ApplicationReady(1.0));
2236 testDevice->ReceivedApplicationReadyResponse());
2238 const auto response =
2239 testDevice->ApplicationReadyResponse();
2242 response.has_value());
2245 response->blockType,
2249 response->controlCommand,
2254 connection->ArUuid());
2257 response->sessionKey,
2258 connection->SessionKey());
2275 testDevice->WaitForConnect());
2278 testDevice->SendApplicationReadyRequest(
2279 connection->ArUuid(),
2280 connection->SessionKey()));
2283 connection->ApplicationReady(1.0));
2285 const auto request =
2286 testDevice->LastApplicationReadyRequest();
2289 request.has_value());
2296 request->controlCommand,
2301 connection->ArUuid());
2304 request->sessionKey,
2305 connection->SessionKey());
2308 request->controlBlockProperties,
2326 testDevice->WaitForConnect());
2329 testDevice->SendApplicationReadyRequest(
2330 connection->ArUuid(),
2331 connection->SessionKey()));
2334 connection->ApplicationReady(1.0));
2336 const auto request =
2337 testDevice->LastApplicationReadyRpcRequest();
2339 const auto response =
2340 testDevice->LastApplicationReadyRpcResponse();
2343 request.has_value());
2346 response.has_value());
2401 testDevice->WaitForConnect());
2404 testDevice->SendApplicationReadyRequest(
2405 connection->ArUuid(),
2406 connection->SessionKey()));
2409 connection->ApplicationReady(1.0));
2412 testDevice->LastApplicationReadyResponseNrd();
2445 testDevice->WaitForConnect());
2448 connection->ApplicationReady(
2449 std::chrono::milliseconds{50}.count() / 1000.0),
2467 testDevice->WaitForConnect());
2469 testDevice->SendRpcRequest(
2472 testDevice->SendApplicationReadyRequest(
2473 connection->ArUuid(),
2474 connection->SessionKey());
2477 connection->ApplicationReady(1.0));
2480 testDevice->ReceivedApplicationReadyResponse());
2482 const auto response =
2483 testDevice->ApplicationReadyResponse();
2486 response.has_value());
2493 response->controlCommand,
2508 testDevice->WaitForConnect());
2510 testDevice->SendRpcResponse(
2513 testDevice->SendApplicationReadyRequest(
2514 connection->ArUuid(),
2515 connection->SessionKey());
2518 connection->ApplicationReady(1.0));
2521 testDevice->ReceivedApplicationReadyResponse());
2523 const auto response =
2524 testDevice->ApplicationReadyResponse();
2527 response.has_value());
2538 response->controlCommand,
2556 testDevice->WaitForConnect());
2558 testDevice->SendControlRequest(
2561 connection->ArUuid(),
2562 connection->SessionKey());
2565 connection->ApplicationReady(
2566 std::chrono::milliseconds{50}.count() / 1000.0),
2570 testDevice->ReceivedApplicationReadyResponse());
2588 testDevice->WaitForConnect());
2590 testDevice->SendControlRequest(
2593 connection->ArUuid(),
2594 connection->SessionKey());
2597 connection->ApplicationReady(1.0));
2600 testDevice->ReceivedApplicationReadyResponse());
2602 const auto response =
2603 testDevice->ApplicationReadyResponse();
2606 response.has_value());
2609 response->blockType,
2613 response->controlCommand,
2618 connection->ArUuid());
2621 response->sessionKey,
2622 connection->SessionKey());
std::unique_ptr< RPCCon > connection
std::optional< dcp::DCPDeviceDescription > info
std::unique_ptr< TestRpcDevice > testDevice
static Bytes BuildApplicationReadyRequestPayload(const std::array< std::uint8_t, uuidLenght > &arUuid, std::uint16_t sessionKey)
void HandleRpcFault(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
PNRPCHeader SendRpcRequest(std::uint16_t operation, const Bytes &payload={}, std::optional< PNRPCHeader > packetTemplate=std::nullopt)
Send an arbitrary RPC REQUEST to the controller.
void HandleRead(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
std::optional< PNRPCHeader > LastApplicationReadyRpcResponse() const
Return the raw RPC ApplicationReady response.
std::atomic_bool sawExpectedSubmodule
TestRpcDevice(ResponseMode mode)
Bytes LastWritePayload() const
bool SawOutputIocr() const
std::atomic< std::size_t > requestCount
bool ReceivedApplicationReadyResponse() const
Check whether the simulated device received an ApplicationReady response.
std::optional< PNRPCHeader > lastApplicationReadyRequestRpc
void SendPacket(const PNRPCHeader &packet)
std::condition_variable stateCondition
bool SawExpectedSubmodule() const
void HandleConnectPnioError(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
std::atomic_bool sawAlarmCr
std::atomic_bool sawExpectedConnect
asio::ip::udp::endpoint ControllerCControlEndpoint() const
std::atomic_bool sawInputIocr
std::optional< PNNRDData > LastApplicationReadyResponseNrd() const
Return the NRD contained in the ApplicationReady response.
void HandleRequest(const Bytes &bytes, const asio::ip::udp::endpoint &sender)
static std::optional< ControlRequest > ParseControlRequest(const PNRPCHeader &request)
std::uint16_t WriteIndex() const
void SendControlRequest(std::uint16_t blockType, std::uint16_t controlCommand, const std::array< std::uint8_t, uuidLenght > &arUuid, std::uint16_t sessionKey, std::uint16_t controlBlockProperties=0)
Send an arbitrary PROFINET CONTROL request.
std::atomic_bool sawOutputIocr
bool SawExpectedConnect() const
void HandleRpcReject(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
asio::io_context ioContext
bool WaitForConnect(std::chrono::milliseconds timeout=std::chrono::milliseconds{1000})
Wait until the simulated device has received a valid CONNECT.
std::optional< ControlRequest > LastApplicationReadyRequest() const
Return the last ApplicationReady request sent by the device.
void HandleApplicationReadyResponse(const PNRPCHeader &response)
std::uint16_t ReadIndex() const
void HandleWrite(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender)
@ ConnectIgnoreRequestThenRespond
void SendRpcResponse(const PNRPCHeader &response, const asio::ip::udp::endpoint &remote)
void SendApplicationReadyRequest(const std::array< std::uint8_t, uuidLenght > &arUuid, std::uint16_t sessionKey)
Send an ApplicationReady request from the simulated device.
std::atomic< std::uint16_t > writeIndex
std::optional< PNRPCHeader > LastApplicationReadyRpcRequest() const
Return the raw RPC ApplicationReady request.
asio::ip::udp::endpoint controllerEndpoint
std::atomic< std::uint16_t > readIndex
std::optional< ControlResponse > applicationReadyResponse
std::size_t RequestCount() const
std::atomic_bool sawWrite
std::optional< PNRPCHeader > lastApplicationReadyResponseRpc
void SendRpcResponse(std::uint16_t operation, const Bytes &payload={}, std::optional< PNRPCHeader > packetTemplate=std::nullopt)
Send an arbitrary RPC RESPONSE to the controller.
static std::optional< ControlResponse > ParseControlResponse(const PNRPCHeader &response)
std::optional< PNRPCHeader > lastConnectRequest
std::optional< ControlResponse > ApplicationReadyResponse() const
Return the parsed ApplicationReady response.
asio::ip::udp::socket socket
bool WaitForApplicationReadyResponse(std::chrono::milliseconds timeout=std::chrono::milliseconds{1000}) const
Wait until an ApplicationReady response was received.
static Bytes BuildControlRequestPayload(std::uint16_t blockType, std::uint16_t controlCommand, const std::array< std::uint8_t, uuidLenght > &arUuid, std::uint16_t sessionKey, std::uint16_t controlBlockProperties)
std::optional< ControlRequest > applicationReadyRequest
bool SawInputIocr() const
std::atomic_bool connected
void HandleConnect(const PNRPCHeader &request, const asio::ip::udp::endpoint &sender, bool sendRequestBeforeResponse, bool checkAlarmCr)
Failed to establish RPC connection.
Parsed PROFINET device information from a DCP response.
std::string name
Station name.
Connection to a PROFINET IO-Device: establishes an AR (Application Relationship) and performs acyclic...
std::uint16_t SessionKey() const
The session key for this connection.
bool HasAlarmCr() const override
Whether an AlarmCR was successfully established.
const dcp::DCPDeviceDescription & Info() const override
The device this connection targets.
void Disconnect() override
Send Release to terminate the AR.
void Close() noexcept override
Disconnect() and close the underlying sockets.
bool IsConnected() const override
Whether the AR is currently established.
std::optional< ConnectResult > Connect(std::optional< MacAddress > srcMac=std::nullopt, bool withAlarmCr=false, std::optional< IOCRSetup > iocrSetup=std::nullopt) override
Establish (or re-establish) the AR with the device.
const std::array< std::uint8_t, uuidLenght > & ArUuid() const
The AR UUID for this connection.
Declares exceptions and error types used by the PROFINET IO controller stack.
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_REQ
Block type: IODControlReqAppReady.
constexpr std::uint16_t CONTROL_CMD_DONE
Control command bit: Done.
constexpr std::uint16_t BLOCK_IOD_CONTROL_PRM_END_REQ
Block type: IODControlReqPrmEnd.
constexpr std::uint16_t RPC_PORT
PROFINET IO RPC port (34964).
constexpr std::uint16_t CONTROL_CMD_PRM_END
Control command bit: PrmEnd.
constexpr std::uint16_t CONTROL_CMD_APP_READY
Control command bit: ApplicationReady.
BlockHeaderView PeekBlockHeader(const Bytes &data, std::size_t offset)
constexpr std::uint16_t BLOCK_IOD_CONTROL_APP_READY_RES
Block type: IODControlResAppReady.
TEST_F(AlarmListenerTests, InitializesWithEndpoint)
constexpr int uuidLenght
Constant lenght of a UUID.
constexpr int blockHeaderLength
Constant lenght of a blockHeader.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
@ ApplicationReady
Application Ready command.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
constexpr std::size_t RECEIVE_BUFFER_LENGTH
Receive buffer length.
PROFINET protocol wire-format structures and serialization helpers.
PROFINET DCE/RPC protocol structures and IO-Device connection API.
std::uint16_t blockLength
std::array< std::uint8_t, uuidLenght > arUuid
std::uint16_t controlBlockProperties
std::uint16_t controlCommand
std::array< std::uint8_t, uuidLenght > arUuid
std::uint16_t controlBlockProperties
std::uint16_t operationNumber
std::uint16_t controlCommand
Control block used for Release/PrmBegin/PrmEnd/ApplicationReady/RTClass3 requests.
std::array< std::uint8_t, uuidLenght > arUuid
AR UUID this control operation applies to.
std::uint16_t controlBlockProperties
Control block property flags.
std::uint16_t padding1
Reserved/padding.
std::uint16_t sessionKey
Session key matching the AR.
std::array< std::uint8_t, blockHeaderLength > blockHeader
Nested 6-byte block header.
ControlCommand controlCommand
Control command bitmask (PrmEnd/AppReady/Release/Done/...).
std::uint16_t padding2
Reserved/padding.
Bytes ToBytes() const
Serialize this block back to raw bytes.
NRD (Network Representation Data) wrapper carrying the actual IOD payload.
Bytes payload
The wrapped IOD data.
static PNNRDData Parse(const Bytes &data)
Parse an NRD wrapper and payload from raw bytes.
Bytes ToBytes() const
Serialize this wrapper and payload back to raw bytes.
std::uint32_t argsLength
Length in bytes of the arguments (usually equals ActualCount).
std::uint32_t offset
Byte offset into the logical result (0 unless fragmented).
std::uint32_t argsMaximumStatus
Maximum status/argument buffer size accepted by the caller.
std::uint32_t maximumCount
Maximum number of bytes the caller can accept.
std::uint32_t actualCount
Actual length in bytes of Payload.
Configuration for establishing cyclic IO alongside an AR.
std::vector< IOSlot > slots
Slots to include in the cyclic data frames.
One slot/subslot's expected module configuration and IO data sizes.
std::uint16_t slot
Slot number.
Configuration options for an RPC connection.
double timeoutSec
Timeout in seconds for RPC operations.
constexpr std::uint16_t kTestSubslot
Bytes ExtractNrdPayload(const PNRPCHeader &request)
bool ContainsBlockType(const Bytes &data, std::uint16_t blockType)
IOCRSetup MakeAumaLikeIocrSetup()
constexpr std::uint16_t kTestAlarmReference
constexpr std::uint16_t kTestRpcPort
PNRPCHeader MakeRpcReject(const PNRPCHeader &request)
constexpr std::uint32_t kTestApi
constexpr std::uint16_t kTestWriteIndex
std::size_t CountBlockType(const Bytes &data, std::uint16_t blockType)
std::vector< BlockView > FindBlocks(const Bytes &data)
Bytes MakeNrdResponse(const Bytes &payload)
Bytes GetBlock(const Bytes &data, const BlockView &block)
PNRPCHeader MakeRpcResponse(const PNRPCHeader &request, const Bytes &payload)
PNRPCHeader MakeRpcFault(const PNRPCHeader &request)
constexpr std::uint16_t kTestSlot
constexpr std::uint16_t kTestReadIndex
dcp::DCPDeviceDescription MakeTestDeviceDescription()
Bytes MakePnioStatusResponse(std::uint32_t argsMaximumStatus, const Bytes &payload={})