From fff1551a31cad0994142896b71062723fa839469 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Mon, 26 Jun 2023 15:54:49 +0300 Subject: [PATCH 01/27] Add JetBrains CLion directory with teh dictionary (CLion is superior to VSCode, and I am not even paid to say that) --- tests/.idea/dictionaries/pavel.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/.idea/dictionaries/pavel.xml diff --git a/tests/.idea/dictionaries/pavel.xml b/tests/.idea/dictionaries/pavel.xml new file mode 100644 index 0000000..86eeadb --- /dev/null +++ b/tests/.idea/dictionaries/pavel.xml @@ -0,0 +1,18 @@ + + + + dudpard + ghcr + libudpard + mmcu + nosonar + opencyphal + stringmakers + udpard + wconversion + wextra + wtype + zzzzzz + + + \ No newline at end of file From 6cb16c86b8388aa9445756a421c9d07d05689d25 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Mon, 26 Jun 2023 15:55:48 +0300 Subject: [PATCH 02/27] Temporarily drop the codebase and the tests, ensure everything builds and the remaining few tests pass --- libudpard/.clang-tidy | 1 + libudpard/{cavl.h => _udpard_cavl.h} | 2 +- libudpard/udpard.c | 1375 +-- tests/.clang-tidy | 1 + tests/CMakeLists.txt | 34 +- tests/catch/catch.hpp | 15651 +++++++++++++++++-------- tests/{catch => }/main.cpp | 2 +- tests/test_private_cavl.cpp | 13 +- tests/test_private_crc.cpp | 53 - tests/test_private_rx.cpp | 753 -- tests/test_private_tx.cpp | 142 - tests/test_public_roundtrip.cpp | 274 - tests/test_public_rx.cpp | 479 - tests/test_public_tx.cpp | 430 - tests/test_self.cpp | 2 +- 15 files changed, 10839 insertions(+), 8373 deletions(-) rename libudpard/{cavl.h => _udpard_cavl.h} (99%) rename tests/{catch => }/main.cpp (97%) delete mode 100644 tests/test_private_crc.cpp delete mode 100644 tests/test_private_rx.cpp delete mode 100644 tests/test_private_tx.cpp delete mode 100644 tests/test_public_roundtrip.cpp delete mode 100644 tests/test_public_rx.cpp delete mode 100644 tests/test_public_tx.cpp diff --git a/libudpard/.clang-tidy b/libudpard/.clang-tidy index d7f291d..bd04775 100644 --- a/libudpard/.clang-tidy +++ b/libudpard/.clang-tidy @@ -21,6 +21,7 @@ Checks: >- -cert-dcl03-c, -hicpp-static-assert, -misc-static-assert, + -modernize-macro-to-enum, CheckOptions: - key: readability-function-cognitive-complexity.Threshold value: '99' diff --git a/libudpard/cavl.h b/libudpard/_udpard_cavl.h similarity index 99% rename from libudpard/cavl.h rename to libudpard/_udpard_cavl.h index c9213b8..8dc8344 100644 --- a/libudpard/cavl.h +++ b/libudpard/_udpard_cavl.h @@ -42,7 +42,7 @@ extern "C" { // ---------------------------------------- PUBLIC API SECTION ---------------------------------------- -/// Modified for use with Libudpard: expose the Cavl structure via public API as UdpardTreeNode. +/// Modified for use with LibUDPard: expose the Cavl structure via public API as UdpardTreeNode. typedef UdpardTreeNode Cavl; /// Returns POSITIVE if the search target is GREATER than the provided node, negative if smaller, zero on match (found). diff --git a/libudpard/udpard.c b/libudpard/udpard.c index ab28b4e..7fd7956 100644 --- a/libudpard/udpard.c +++ b/libudpard/udpard.c @@ -5,1378 +5,5 @@ /// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. #include "udpard.h" -#include "cavl.h" +#include "_udpard_cavl.h" #include - -// --------------------------------------------- BUILD CONFIGURATION --------------------------------------------- - -/// Define this macro to include build configuration header. -/// Usage example with CMake: "-DUDPARD_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/my_udpard_config.h\"" -#ifdef UDPARD_CONFIG_HEADER -# include UDPARD_CONFIG_HEADER -#endif - -/// By default, this macro resolves to the standard assert(). The user can redefine this if necessary. -/// To disable assertion checks completely, make it expand into `(void)(0)`. -#ifndef UDPARD_ASSERT -// Intentional violation of MISRA: inclusion not at the top of the file to eliminate unnecessary dependency on assert.h. -# include // NOSONAR -// Intentional violation of MISRA: assertion macro cannot be replaced with a function definition. -# define UDPARD_ASSERT(x) assert(x) // NOSONAR -#endif - -/// Define UDPARD_CRC_TABLE=0 to use slow but ROM-efficient transfer-CRC computation algorithm. -/// Doing so is expected to save ca. 500 bytes of ROM and increase the cost of RX/TX transfer processing by ~half. -#ifndef UDPARD_CRC_TABLE -# define UDPARD_CRC_TABLE 1 -#endif - -/// This macro is needed for testing and for library development. -#ifndef UDPARD_PRIVATE -# define UDPARD_PRIVATE static inline -#endif - -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# error "Unsupported language: ISO C99 or a newer version is required." -#endif - -// --------------------------------------------- COMMON DEFINITIONS --------------------------------------------- - -static const uint8_t BITS_PER_BYTE = 8U; -static const uint8_t BYTE_MAX = 0xFFU; - -static const uint8_t UDPARD_END_OF_TRANSFER_OFFSET = 31U; -static const uint32_t UDPARD_MAX_FRAME_INDEX = 0x7FFFFFFFU; // ((1U << UDPARD_END_OF_TRANSFER_OFFSET) - 1U); -static const uint16_t UDPARD_NODE_ID_MASK = 65535U; /// 0xFFFF - -/* - fixed Cyphal/UDP - (9 bits) Ipv4 Addr - Version Destination Node ID - ________________(1 bit) SNM _______________________________ - / \ | | / \ - 1 1 1 0 1 1 1 1 . 0 c d d d d d m . 0 s s s s s s s . s s s s s s s s - \_____/ \______/ \_______/ | \_____________________________/ - (4 bits) (4 bits) Subnet | (15 bits) subject-ID - IPv4 Scope Reserved Reserved - multicast \_______________________________________________/ - prefix (23 bits) - collision-free multicast - addressing limit of - Ethernet MAC for IPv4 -*/ - -/// The multicast message transfer IP address node ID is formed of 1 reserved 0 bits and 15 bits for a subject id. -static const uint16_t UDPARD_SUBJECT_ID_MASK = 0x7FFFU; -static const uint32_t UDPARD_SUBNET_MASK = 0x3E0000U; // (31U << UDPARD_SUBNET_OFFSET (17)); -static const uint8_t UDPARD_TRANSMIT_SUBNET_VALUE = 0U; -static const uint16_t UDPARD_RESERVED_1BIT_MASK = 0x8000U; // (1U << UDPARD_RESERVED_1BIT_OFFSET (15)); -static const uint32_t UDPARD_SERVICE_NOT_MESSAGE_MASK = 0x10000U; // (1U << UDPARD_SERVICE_NOT_MESSAGE_OFFSET (16)); -static const uint32_t UDPARD_MULTICAST_PREFIX = 0xEF000000U; // (478U << UDPARD_MULTICAST_OFFSET (23)) - -/* The 16 bit data specifier in the Cyphal header consists of -SNM + 15 bit Subject-ID (Message) -SNM + IRNR + Service-ID (Service Request/Response) - -SNM - Service, Not Message -IRNR - Is Request, Not Response -*/ -static const uint16_t UDPARD_SERVICE_NOT_MESSAGE_DATA_SPECIFIER_OFFSET = 15U; -static const uint16_t UDPARD_IRNR_DATA_SPECIFIER_OFFSET = 14U; -static const uint16_t UDPARD_SERVICE_ID_MASK = 0x3FFFU; -static const uint16_t UDPARD_DATA_SPECIFIER_MESSAGE_MASK = 0x7FFFU; // SNM (0) + SubjectID -static const uint16_t UDPARD_DATA_SPECIFIER_SERVICE_RESPONSE = 0x8000U; // (2U << UDPARD_IRNR_DATA_SPECIFIER_OFFSET) // Set SNM in Cyphal data specifier - SNM (1) + IRNR (0) + ServiceID -static const uint16_t UDPARD_DATA_SPECIFIER_SERVICE_REQUEST = 0xC000U; // (3U << UDPARD_IRNR_DATA_SPECIFIER_OFFSET) // Set SNM and IRNR in Cyphal data specifier - SNM (1) + IRNR (1) + ServiceID - -static const uint16_t UDPARD_UDP_PORT = 9382U; - -/// Used for inserting new items into AVL trees. -UDPARD_PRIVATE UdpardTreeNode* avlTrivialFactory(void* const user_reference) -{ - return (UdpardTreeNode*) user_reference; -} - -/// --------------------------------------------- TRANSFER CRC --------------------------------------------- - -typedef uint32_t TransferCRC; - -// Those crc values come from https://crc32c.machinezoo.com/ -static const TransferCRC CRC_INITIAL = 0xFFFFFFFFU; -static const TransferCRC CRC_RESIDUE = 0xB798B438U; -static const TransferCRC CRC_XOR = 0xFFFFFFFFU; -static const size_t CRC_SIZE_BYTES = 4U; - -static const uint8_t CRC_BYTE_MASK = 0xFFU; - -static const uint32_t CRCTable[256] = - {0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, - 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c, - 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, - 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, - 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, - 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, - 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, - 0x6ef07595, 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, - 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, 0x5125dad3, - 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f, - 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, 0x61c69362, 0x93ad1061, 0x80fde395, - 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, - 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, - 0x44694011, 0x5739b3e5, 0xa55230e6, 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, - 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, - 0x563c5f93, 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, - 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, 0x1871a4d8, - 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5, - 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, - 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, - 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, - 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, - 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, - 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, - 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, - 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6, - 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, 0xf36e6f75, 0x0105ec76, 0x12551f82, - 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, - 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351}; - -UDPARD_PRIVATE TransferCRC crcAddByte(const TransferCRC crc, const uint8_t byte) -{ - return CRCTable[(uint32_t) ( byte ^ (crc & BYTE_MAX))] ^ (crc >> BITS_PER_BYTE); -} - -UDPARD_PRIVATE TransferCRC crcAdd(const TransferCRC crc, const size_t size, const void* const data) -{ - UDPARD_ASSERT((data != NULL) || (size == 0U)); - TransferCRC out = crc; - const uint8_t* p = (const uint8_t*) data; - for (size_t i = 0; i < size; i++) - { - out = crcAddByte(out, *p); - ++p; - } - return out; -} - -UDPARD_PRIVATE TransferCRC crcValue(const TransferCRC crc) -{ - return (uint32_t) (crc ^ CRC_XOR); -} - -/// --------------------------------------------- CYPHAL HEADER CRC --------------------------------------------- - -typedef uint16_t CyphalHeaderCRC; - -// Based on CRC-16-CCITT-FALSE Function -static const uint16_t CYPHAL_HEADER_CRC_INITIAL = 0xFFFFU; -static const size_t CYPHAL_HEADER_CRC_SIZE_BYTES = 2U; - -UDPARD_PRIVATE CyphalHeaderCRC cyphalHeaderCrcAddByte(const CyphalHeaderCRC crc, const uint8_t byte) -{ - // Based on CRC-16-CCITT-FALSE Function - static const CyphalHeaderCRC Top = 0x8000U; - static const CyphalHeaderCRC Poly = 0x1021U; - CyphalHeaderCRC out = crc ^ (uint16_t) ((uint16_t) (byte) << BITS_PER_BYTE); - // Do not fold this into a loop because a size-optimizing compiler won't unroll it degrading the performance. - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - return out; -} - -UDPARD_PRIVATE CyphalHeaderCRC cyphalHeaderCrcAdd(const CyphalHeaderCRC crc, const size_t size, const void* const header) -{ - UDPARD_ASSERT(header != NULL); - CyphalHeaderCRC out = crc; - const uint8_t* p = (const uint8_t*) header; - for (size_t i = 0; i < size; i++) - { - out = cyphalHeaderCrcAddByte(out, *p); - ++p; - } - return out; -} - -// --------------------------------------------- TRANSMISSION --------------------------------------------- - -/// This is a subclass of UdpardTxQueueItem. A pointer to this type can be cast to UdpardTxQueueItem safely. -/// This is standard-compliant. The paragraph 6.7.2.1.15 says: -/// A pointer to a structure object, suitably converted, points to its initial member (or if that member is a -/// bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a -/// structure object, but not at its beginning. -typedef struct TxItem -{ - UdpardTxQueueItem base; - uint8_t payload_buffer[UDPARD_MTU_MAX]; -} TxItem; - -/// Chain of TX frames prepared for insertion into a TX queue. -typedef struct -{ - TxItem* head; - TxItem* tail; - size_t size; -} TxChain; - -/// TODO - Determine what is needed for a Message Sessions Specifier -UDPARD_PRIVATE int32_t txMakeMessageSessionSpecifier(const UdpardPortID subject_id, - const UdpardNodeID src_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const out_spec) -{ - UDPARD_ASSERT(subject_id <= UDPARD_SUBJECT_ID_MAX); - /// Just the local ip address + source node id - out_spec->source_route_specifier = - (local_node_addr & ~(UdpardIPv4Addr) UDPARD_NODE_ID_MASK) | - (UdpardIPv4Addr) src_node_id; - out_spec->destination_route_specifier = - ((UDPARD_TRANSMIT_SUBNET_VALUE & (UdpardIPv4Addr) UDPARD_SUBNET_MASK) | - (UdpardIPv4Addr) UDPARD_MULTICAST_PREFIX | - ((UdpardIPv4Addr) UDPARD_SUBJECT_ID_MASK & (UdpardIPv4Addr) subject_id)) & - ~(UdpardIPv4Addr) UDPARD_SERVICE_NOT_MESSAGE_MASK & - ~(UdpardIPv4Addr) UDPARD_RESERVED_1BIT_MASK; - out_spec->data_specifier = (UdpardUdpPortID) UDPARD_UDP_PORT; - return UDPARD_SUCCESS; -} - -UDPARD_PRIVATE int32_t txMakeServiceSessionSpecifier(const UdpardPortID service_id, - const UdpardNodeID src_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const out_spec) -{ - UDPARD_ASSERT(service_id < UDPARD_SERVICE_ID_MAX); - /// Just the local ip address + source node id - out_spec->source_route_specifier = - (local_node_addr & ~(UdpardIPv4Addr) UDPARD_NODE_ID_MASK) | - (UdpardIPv4Addr) src_node_id; - out_spec->destination_route_specifier = - ((UDPARD_TRANSMIT_SUBNET_VALUE & (UdpardIPv4Addr) UDPARD_SUBNET_MASK) | - (UdpardIPv4Addr) UDPARD_MULTICAST_PREFIX | - ((UdpardIPv4Addr) UDPARD_NODE_ID_MASK & (UdpardIPv4Addr) service_id)) | - (UdpardIPv4Addr) UDPARD_SERVICE_NOT_MESSAGE_MASK; - out_spec->data_specifier = (UdpardUdpPortID) UDPARD_UDP_PORT; - return UDPARD_SUCCESS; -} - -// This may need to be adjusted... -UDPARD_PRIVATE size_t adjustPresentationLayerMTU(const size_t mtu_bytes) -{ - if (mtu_bytes >= UDPARD_MTU_UDP_IPV4) - { - return UDPARD_MTU_UDP_IPV4; - } - return mtu_bytes; -} - -UDPARD_PRIVATE int32_t txMakeSessionSpecifier(const UdpardTransferMetadata* const tr, - const UdpardNodeID local_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const spec) -{ - UDPARD_ASSERT(tr != NULL); - int32_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - if ((tr->transfer_kind == UdpardTransferKindMessage) && (UDPARD_NODE_ID_UNSET == tr->remote_node_id) && - (tr->port_id <= UDPARD_SUBJECT_ID_MAX)) - { - if ((local_node_id == UDPARD_NODE_ID_UNSET) || (local_node_id <= UDPARD_NODE_ID_MAX)) { - - out = txMakeMessageSessionSpecifier(tr->port_id, local_node_id, local_node_addr, spec); - UDPARD_ASSERT(out >= 0); - } else { - out = -UDPARD_ERROR_INVALID_ARGUMENT; // Node must be at least 1 less than the unset id - } - } - else if (((tr->transfer_kind == UdpardTransferKindRequest) || (tr->transfer_kind == UdpardTransferKindResponse)) && - (tr->port_id < UDPARD_SERVICE_ID_MAX) && (tr->remote_node_id != UDPARD_NODE_ID_UNSET)) - { - if (local_node_id != UDPARD_NODE_ID_UNSET) - { - out = txMakeServiceSessionSpecifier(tr->port_id, - local_node_id, - local_node_addr, - spec); - UDPARD_ASSERT(out >= 0); - } - else - { - out = -UDPARD_ERROR_INVALID_ARGUMENT; // Anonymous service transfers are not allowed. - } - } - else - { - out = -UDPARD_ERROR_INVALID_ARGUMENT; - } - - if (out >= 0) - { - const uint32_t prio = (uint32_t) tr->priority; - if (prio > UDPARD_PRIORITY_MAX) - { - out = -UDPARD_ERROR_INVALID_ARGUMENT; // Priority can't be greater than max value - } - } - return out; -} - -UDPARD_PRIVATE void txMakeFrameHeader(UdpardFrameHeader* const header, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const bool end_of_transfer, - const uint32_t frame_index) -{ - UDPARD_ASSERT(frame_index <= UDPARD_MAX_FRAME_INDEX); - uint32_t end_of_transfer_mask = (uint32_t) (end_of_transfer ? 1 : 0) << (uint32_t) UDPARD_END_OF_TRANSFER_OFFSET; - size_t cyphal_header_size_without_crc = sizeof(UdpardFrameHeader) - CYPHAL_HEADER_CRC_SIZE_BYTES; - header->transfer_id = transfer_id; - header->version = (uint8_t) UDPARD_CYPHAL_HEADER_VERSION; - header->priority = (uint8_t) priority; - header->frame_index_eot = end_of_transfer_mask | frame_index; - header->source_node_id = src_node_id; - header->destination_node_id = dst_node_id; - header->cyphal_header_checksum = cyphalHeaderCrcAdd(CYPHAL_HEADER_CRC_INITIAL, cyphal_header_size_without_crc, header); - if (transfer_kind == UdpardTransferKindMessage) - { - // both port_id and the data_specifier start at bit-0. No shift of the port_id value is necessary. - header->data_specifier = port_id & UDPARD_DATA_SPECIFIER_MESSAGE_MASK; - } - else - { - // SNM (1) + IRNR + ServiceID - if (transfer_kind == UdpardTransferKindRequest) - { - header->data_specifier = UDPARD_DATA_SPECIFIER_SERVICE_REQUEST | port_id; - } - else - { - header->data_specifier = UDPARD_DATA_SPECIFIER_SERVICE_RESPONSE | port_id; - } - } -} - -/// The item is only allocated and initialized, but NOT included into the queue! The caller needs to do that. -UDPARD_PRIVATE TxItem* txAllocateQueueItem(UdpardInstance* const ins, - const UdpardSessionSpecifier* const spec, - const UdpardMicrosecond deadline_usec, - const size_t payload_size) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(payload_size > 0U); - TxItem* const out = (TxItem*) ins->memory_allocate(ins, sizeof(TxItem) - UDPARD_MTU_MAX + payload_size); - if (out != NULL) - { - out->base.base.up = NULL; - out->base.base.lr[0] = NULL; - out->base.base.lr[1] = NULL; - out->base.base.bf = 0; - - out->base.next_in_transfer = NULL; // Last by default. - out->base.tx_deadline_usec = deadline_usec; - - out->base.frame.payload_size = payload_size; - out->base.frame.payload = out->payload_buffer; - out->base.specifier.data_specifier = spec->data_specifier; - out->base.specifier.destination_route_specifier = spec->destination_route_specifier; - out->base.specifier.source_route_specifier = spec->source_route_specifier; - } - return out; -} - -/// Frames with identical UDP ID that are added later always compare greater than their counterparts with same UDP ID. -/// This ensures that UDP frames with the same UDP ID are transmitted in the FIFO order. -/// Frames that should be transmitted earlier compare smaller (i.e., put on the left side of the tree). -UDPARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) -{ - const UdpardTxQueueItem* const target = (const UdpardTxQueueItem*) user_reference; - const UdpardTxQueueItem* const other = (const UdpardTxQueueItem*) node; - UDPARD_ASSERT((target != NULL) && (other != NULL)); - if (target->frame.udp_cyphal_header.priority > other->frame.udp_cyphal_header.priority) - { - return +1; - } - if (target->frame.udp_cyphal_header.transfer_id >= other->frame.udp_cyphal_header.transfer_id) - { - return +1; - } - return -1; -} - -/// Returns the number of frames enqueued or error (i.e., =1 or <0). -UDPARD_PRIVATE int32_t txPushSingleFrame(UdpardTxQueue* const que, - UdpardInstance* const ins, - const UdpardMicrosecond deadline_usec, - const UdpardSessionSpecifier* const specifier, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const size_t payload_size, - const void* const payload) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT((payload != NULL) || (payload_size == 0)); - // The size of a Frame header shouldn't change, but best to check it is at least bigger than 0 - UDPARD_ASSERT(sizeof(UdpardFrameHeader) > 0); // NOLINT - const size_t frame_payload_size = payload_size + sizeof(UdpardFrameHeader) + CRC_SIZE_BYTES; - UDPARD_ASSERT(frame_payload_size > payload_size); - - int32_t out = 0; - uint32_t frame_index = 0U; - bool end_of_transfer = true; - - TxItem* const tqi = - (que->size < que->capacity) ? txAllocateQueueItem(ins, specifier, deadline_usec, frame_payload_size) : NULL; - if (tqi != NULL) - { - if (payload_size > 0U) // The check is needed to avoid calling memcpy() with a NULL pointer, it's an UB. - { - UDPARD_ASSERT(payload != NULL); - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&tqi->payload_buffer[sizeof(UdpardFrameHeader)], payload, payload_size); // NOLINT - } - /// Create the FrameHeader - txMakeFrameHeader( - &tqi->base.frame.udp_cyphal_header, - src_node_id, - dst_node_id, - port_id, - transfer_kind, - priority, - transfer_id, - end_of_transfer, - frame_index); - - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&tqi->payload_buffer[0], &tqi->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - - // Insert CRC - size_t frame_offset = payload_size + sizeof(UdpardFrameHeader); - TransferCRC crc = crcValue(crcAdd(CRC_INITIAL, payload_size, payload)); - uint8_t crc_as_byte[] = {(uint8_t)(crc & BYTE_MAX & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 1U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 2U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 3U) & CRC_BYTE_MASK)}; - for (unsigned int i = 0; i < sizeof(crc_as_byte); i++) - { - tqi->payload_buffer[frame_offset++] = crc_as_byte[i]; - } - - // Insert the newly created TX item into the queue. - const UdpardTreeNode* const res = cavlSearch(&que->root, &tqi->base.base, &txAVLPredicate, &avlTrivialFactory); - (void) res; - UDPARD_ASSERT(res == &tqi->base.base); - que->size++; - UDPARD_ASSERT(que->size <= que->capacity); - out = 1; // One frame enqueued. - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - UDPARD_ASSERT((out < 0) || (out == 1)); - return out; -} - -// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM. -UDPARD_PRIVATE TxChain txGenerateMultiFrameChain(UdpardInstance* const ins, - const size_t presentation_layer_mtu, - const UdpardMicrosecond deadline_usec, - const UdpardSessionSpecifier* const specifier, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const size_t payload_size, - const void* const payload) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(presentation_layer_mtu > sizeof(UdpardFrameHeader)); - - // Otherwise, a single-frame transfer should be used. - // CRC_SIZE_BYTES - UDPARD_ASSERT(payload_size + CRC_SIZE_BYTES > presentation_layer_mtu - sizeof(UdpardFrameHeader)); - UDPARD_ASSERT(payload != NULL); - - TxChain out = {NULL, NULL, 0}; - const size_t payload_size_with_crc = payload_size + CRC_SIZE_BYTES; - size_t offset = 0U; - TransferCRC crc = crcValue(crcAdd(CRC_INITIAL, payload_size, payload)); - const uint8_t* payload_ptr = (const uint8_t*) payload; - uint32_t frame_index = 0U; - uint8_t crc_as_byte[] = {(uint8_t)(crc & BYTE_MAX & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 1U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 2U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 3U) & CRC_BYTE_MASK)}; - size_t last_crc_index = 0U; - size_t inserted_crc_amount = 0; - while (offset < payload_size_with_crc) - { - out.size++; - size_t frame_payload_size_with_header = 0U; - frame_payload_size_with_header = - ((payload_size_with_crc - offset + sizeof(UdpardFrameHeader)) < presentation_layer_mtu) - ? (payload_size_with_crc - offset + sizeof(UdpardFrameHeader)) - : (presentation_layer_mtu); - - TxItem* const tqi = txAllocateQueueItem(ins, specifier, deadline_usec, frame_payload_size_with_header); - if (NULL == out.head) - { - out.head = tqi; - } - else - { - out.tail->base.next_in_transfer = (UdpardTxQueueItem*) tqi; - } - out.tail = tqi; - // If the queue item is not generated successfully, - // we'll end the while loop early to stop the chain - // generation. - // We'll do the memory clean up after this function is done. - if (NULL == out.tail) - { - break; - } - const size_t frame_payload_size = frame_payload_size_with_header; - size_t frame_offset = 0U; - // Index to start copying payload to the frame. - size_t start_index = offset; - // Size of remaining payload + CRC - // We'll compare the size of remaining payload+crc and available size of current frame - // and set the move_size_with_crc to the smaller one. - size_t move_size_with_crc = payload_size_with_crc - offset - >= frame_payload_size - sizeof(UdpardFrameHeader) - ? frame_payload_size - sizeof(UdpardFrameHeader) - : payload_size_with_crc - offset; - // Index referring the end of copied payload with CRC. - size_t end_index = start_index + move_size_with_crc; - // True if all payload has been process and is ready to insert CRC - bool initial_payload_overrun = (end_index >= payload_size); - // Number of CRC byte that has been inserted. - size_t overrun_amount = ((initial_payload_overrun) ? end_index - payload_size : inserted_crc_amount) - inserted_crc_amount; - inserted_crc_amount += overrun_amount; - // Size of remaining payload without CRC - size_t payload_move_size = move_size_with_crc - overrun_amount; - - txMakeFrameHeader( - &out.tail->base.frame.udp_cyphal_header, - src_node_id, - dst_node_id, - port_id, - transfer_kind, - priority, - transfer_id, - false, - frame_index); - - if(initial_payload_overrun) - { - // Insert payload and header - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&out.tail->payload_buffer[0], &out.tail->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - (void) memcpy(&out.tail->payload_buffer[sizeof(UdpardFrameHeader)], payload_ptr, payload_move_size); // NOLINT - frame_offset += sizeof(UdpardFrameHeader) + payload_move_size; - // Insert crc into same frame - for(unsigned int i = 0; i < overrun_amount; i++) - { - out.tail->payload_buffer[frame_offset++] = crc_as_byte[i + last_crc_index]; - } - last_crc_index += overrun_amount; - } - else - { - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&out.tail->payload_buffer[0], &out.tail->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - (void) memcpy(&out.tail->payload_buffer[sizeof(UdpardFrameHeader)], payload_ptr, payload_move_size); // NOLINT - frame_offset += sizeof(UdpardFrameHeader) + payload_move_size; - } - payload_ptr += payload_move_size; - offset += move_size_with_crc; - - // Deal with last frame after inserting all payload and crc - if(end_index == payload_size_with_crc) - { - out.tail->base.frame.udp_cyphal_header.frame_index_eot = (uint32_t) (1U << (uint32_t) UDPARD_END_OF_TRANSFER_OFFSET) + frame_index; - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&out.tail->payload_buffer[0], &out.tail->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - UDPARD_ASSERT((frame_offset) == out.tail->base.frame.payload_size); - } - ++frame_index; - } - return out; -} - -/// Returns the number of frames enqueued or error. -UDPARD_PRIVATE int32_t txPushMultiFrame(UdpardTxQueue* const que, - UdpardInstance* const ins, - const size_t presentation_layer_mtu, - const UdpardMicrosecond deadline_usec, - const UdpardSessionSpecifier* const specifier, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const size_t payload_size, - const void* const payload) -{ - - UDPARD_ASSERT((ins != NULL) && (que != NULL)); - UDPARD_ASSERT(presentation_layer_mtu > sizeof(UdpardFrameHeader)); - UDPARD_ASSERT(payload_size + CRC_SIZE_BYTES > presentation_layer_mtu - sizeof(UdpardFrameHeader)); - const size_t payload_size_with_crc = payload_size + CRC_SIZE_BYTES; - const size_t num_frames = (payload_size_with_crc - + presentation_layer_mtu - - sizeof(UdpardFrameHeader) - 1) - / (presentation_layer_mtu - - sizeof(UdpardFrameHeader)); - UDPARD_ASSERT(num_frames >= 2); - - int32_t out = 0; - if ((que->size + num_frames) <= que->capacity) - { - const TxChain sq = txGenerateMultiFrameChain(ins, - presentation_layer_mtu, - deadline_usec, - specifier, - src_node_id, - dst_node_id, - port_id, - transfer_kind, - priority, - transfer_id, - payload_size, - payload); - if (sq.tail != NULL) - { - UdpardTxQueueItem* next = &sq.head->base; - do - { - const UdpardTreeNode* const res = cavlSearch(&que->root, &next->base, &txAVLPredicate, &avlTrivialFactory); - (void) res; - UDPARD_ASSERT(res == &next->base); - UDPARD_ASSERT(que->root != NULL); - next = next->next_in_transfer; - } while (next != NULL); - UDPARD_ASSERT(num_frames == sq.size); - que->size += sq.size; - UDPARD_ASSERT(que->size <= que->capacity); - UDPARD_ASSERT((sq.size + 0ULL) <= INT32_MAX); - out = (int32_t) sq.size; - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - UdpardTxQueueItem* head = &sq.head->base; - // We'll clean up the memory after txGenerateMultiFrameChain is done - // with an UDPARD_ERROR_OUT_OF_MEMORY error raised. - while (head != NULL) - { - UdpardTxQueueItem* const next = head->next_in_transfer; - ins->memory_free(ins, head); - head = next; - } - } - - } - else // We predict that we're going to run out of queue, don't bother serializing the transfer. - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - UDPARD_ASSERT((out < 0) || (out >= 2)); - return out; -} - -// --------------------------------------------- RECEPTION --------------------------------------------- - -#define RX_SESSIONS_PER_SUBSCRIPTION (UDPARD_NODE_ID_MAX + 1U) - -/// The memory requirement model provided in the documentation assumes that the maximum size of this structure never -/// exceeds 48 bytes on any conventional platform. -/// A user that needs a detailed analysis of the worst-case memory consumption may compute the size of this -/// structure for the particular platform at hand manually or by evaluating its sizeof(). The fields are ordered to -/// minimize the amount of padding on all conventional platforms. -typedef struct UdpardInternalRxSession -{ - UdpardMicrosecond transfer_timestamp_usec; ///< Timestamp of the last received start-of-transfer. - size_t total_payload_size; ///< The payload size before the implicit truncation, including the CRC. - size_t payload_size; ///< How many bytes received so far. - uint8_t* payload; ///< Dynamically allocated and handed off to the application when done. - TransferCRC calculated_crc; ///< Updated with the received payload in real time. - UdpardTransferID transfer_id; - uint8_t redundant_transport_index; ///< Arbitrary value in [0, 255]. - uint32_t last_udp_header_index; -} UdpardInternalRxSession; - -/// High-level transport frame model. -typedef struct -{ - UdpardMicrosecond timestamp_usec; - UdpardHeaderVersion version; - UdpardPriority priority; - UdpardTransferKind transfer_kind; - UdpardPortID port_id; - UdpardNodeID source_node_id; - UdpardNodeID destination_node_id; - UdpardTransferID transfer_id; - bool start_of_transfer; - bool end_of_transfer; - size_t payload_size; - const void* payload; - uint32_t frame_index; -} RxFrameModel; - -UDPARD_PRIVATE UdpardPortID getPortIdFromDataSpecifiers(UdpardUdpPortID data_specifier) -{ - if ((uint16_t)(data_specifier >> UDPARD_SERVICE_NOT_MESSAGE_DATA_SPECIFIER_OFFSET) & 1U) - { - return (UdpardPortID) (data_specifier & UDPARD_SERVICE_ID_MASK); - } - return (UdpardPortID) (data_specifier & UDPARD_SUBJECT_ID_MASK); -} - -UDPARD_PRIVATE UdpardTransferKind getTransferKindFromDataSpecifier(UdpardUdpPortID data_specifier) -{ - if ((uint16_t)(data_specifier >> UDPARD_SERVICE_NOT_MESSAGE_DATA_SPECIFIER_OFFSET) & 1U) - { - return ((uint16_t)(data_specifier >> UDPARD_IRNR_DATA_SPECIFIER_OFFSET) & 1U) ? UdpardTransferKindRequest : UdpardTransferKindResponse; - } - return UdpardTransferKindMessage; -} - -/// Returns truth if the frame is valid and parsed successfully. False if the frame is not a valid Cyphal/UDP frame. -UDPARD_PRIVATE bool rxTryParseFrame(const UdpardMicrosecond timestamp_usec, - UdpardFrame* const frame, - RxFrameModel* const out) -{ - - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(out != NULL); - if (frame->payload_size < sizeof(frame->udp_cyphal_header)) - { - return false; - } - bool valid = true; - - // Get the Header out of the frame - UDPARD_ASSERT(frame->payload != NULL); - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&frame->udp_cyphal_header, frame->payload, sizeof(frame->udp_cyphal_header)); // NOLINT - out->timestamp_usec = timestamp_usec; - - out->version = frame->udp_cyphal_header.version; - out->priority = (UdpardPriority) frame->udp_cyphal_header.priority; - out->source_node_id = frame->udp_cyphal_header.source_node_id; - out->transfer_kind = getTransferKindFromDataSpecifier(frame->udp_cyphal_header.data_specifier); - out->port_id = getPortIdFromDataSpecifiers(frame->udp_cyphal_header.data_specifier); - out->destination_node_id = frame->udp_cyphal_header.destination_node_id; - // Payload parsing. - out->payload_size = frame->payload_size - sizeof(frame->udp_cyphal_header); // Cut off the header size. - out->payload = (void*) ((uint8_t*) frame->payload + sizeof(frame->udp_cyphal_header)); - - out->transfer_id = frame->udp_cyphal_header.transfer_id; - out->start_of_transfer = (((frame->udp_cyphal_header.frame_index_eot) & (UDPARD_MAX_FRAME_INDEX)) == 0); - out->end_of_transfer = ((frame->udp_cyphal_header.frame_index_eot >> UDPARD_END_OF_TRANSFER_OFFSET) == 1); - out->frame_index = frame->udp_cyphal_header.frame_index_eot; - // Make sure header version is supported - valid = valid && (out->version >= UDPARD_CYPHAL_HEADER_VERSION); - if (out->transfer_kind != UdpardTransferKindMessage) - { - valid = valid && (out->source_node_id != out->destination_node_id); - } - // Anonymous transfers can be only single-frame transfers. - valid = - valid && ((out->start_of_transfer && out->end_of_transfer) || (UDPARD_NODE_ID_UNSET != out->source_node_id)); - // A frame that is a part of a multi-frame transfer cannot be empty (tail byte not included). - valid = valid && ((out->payload_size > 0) || (out->start_of_transfer && out->end_of_transfer)); - return valid; -} - -UDPARD_PRIVATE void rxInitTransferMetadataFromFrame(const RxFrameModel* const frame, - UdpardTransferMetadata* const out_transfer) -{ - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(frame->payload != NULL); - UDPARD_ASSERT(out_transfer != NULL); - out_transfer->priority = frame->priority; - out_transfer->transfer_kind = frame->transfer_kind; - out_transfer->port_id = frame->port_id; - out_transfer->remote_node_id = frame->source_node_id; - out_transfer->transfer_id = frame->transfer_id; -} - -// Assume we will never roll over a transfer id with 64bits -UDPARD_PRIVATE uint64_t rxComputeTransferIDDifference(const uint64_t a, const uint64_t b) -{ - UDPARD_ASSERT(a <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT(b <= UDPARD_TRANSFER_ID_MAX); - return a - b; -} - -UDPARD_PRIVATE int8_t rxSessionWritePayload(UdpardInstance* const ins, - UdpardInternalRxSession* const rxs, - const size_t extent, - const size_t payload_size, - const void* const payload) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - UDPARD_ASSERT((payload != NULL) || (payload_size == 0U)); - UDPARD_ASSERT(rxs->payload_size <= extent); // This invariant is enforced by the subscription logic. - UDPARD_ASSERT(rxs->payload_size <= rxs->total_payload_size); - - rxs->total_payload_size += payload_size; - - // Allocate the payload lazily, as late as possible. - if ((NULL == rxs->payload) && (extent > 0U)) - { - UDPARD_ASSERT(rxs->payload_size == 0); - rxs->payload = ins->memory_allocate(ins, extent); - } - - int8_t out = 0; - if (rxs->payload != NULL) - { - // Copy the payload into the contiguous buffer. Apply the implicit truncation rule if necessary. - size_t bytes_to_copy = payload_size; - if ((rxs->payload_size + bytes_to_copy) > extent) - { - UDPARD_ASSERT(rxs->payload_size <= extent); - bytes_to_copy = extent - rxs->payload_size; - UDPARD_ASSERT((rxs->payload_size + bytes_to_copy) == extent); - UDPARD_ASSERT(bytes_to_copy < payload_size); - } - // This memcpy() call here is one of the two variable-complexity operations in the RX pipeline; - // the other one is the search of the matching subscription state. - // Excepting these two cases, the entire RX pipeline contains neither loops nor recursion. - // Intentional violation of MISRA: indexing on a pointer. This is done to avoid pointer arithmetics. - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&rxs->payload[rxs->payload_size], payload, bytes_to_copy); // NOLINT NOSONAR - rxs->payload_size += bytes_to_copy; - UDPARD_ASSERT(rxs->payload_size <= extent); - } - else - { - UDPARD_ASSERT(rxs->payload_size == 0); - out = (extent > 0U) ? -UDPARD_ERROR_OUT_OF_MEMORY : 0; - } - UDPARD_ASSERT(out <= 0); - return out; -} - -UDPARD_PRIVATE void rxSessionRestart(UdpardInstance* const ins, UdpardInternalRxSession* const rxs) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - ins->memory_free(ins, rxs->payload); // May be NULL, which is OK. - rxs->total_payload_size = 0U; - rxs->payload_size = 0U; - rxs->payload = NULL; - rxs->calculated_crc = CRC_INITIAL; - rxs->transfer_id = (UdpardTransferID) (rxs->transfer_id + 1U) & UDPARD_TRANSFER_ID_MAX; -} - -UDPARD_PRIVATE int8_t rxSessionAcceptFrame(UdpardInstance* const ins, - UdpardInternalRxSession* const rxs, - const RxFrameModel* const frame, - const size_t extent, - UdpardRxTransfer* const out_transfer) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(frame->payload != NULL); - UDPARD_ASSERT(frame->transfer_id <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT(out_transfer != NULL); - - if (frame->start_of_transfer) // The transfer timestamp is the timestamp of its first frame. - { - rxs->transfer_timestamp_usec = frame->timestamp_usec; - } - - rxs->calculated_crc = crcAdd(rxs->calculated_crc, frame->payload_size, frame->payload); - - int8_t out = rxSessionWritePayload(ins, rxs, extent, frame->payload_size, frame->payload); - if (out < 0) - { - UDPARD_ASSERT(-UDPARD_ERROR_OUT_OF_MEMORY == out); - rxSessionRestart(ins, rxs); // Out-of-memory. - } - else if (frame->end_of_transfer) - { - UDPARD_ASSERT(0 == out); - if (CRC_RESIDUE == rxs->calculated_crc) - { - - out = 1; // One transfer received, notify the application. - rxInitTransferMetadataFromFrame(frame, &out_transfer->metadata); - out_transfer->timestamp_usec = rxs->transfer_timestamp_usec; - out_transfer->payload_size = rxs->payload_size; - out_transfer->payload = rxs->payload; - - // Cut off the CRC from the payload if it's there -- we don't want to expose it to the user. - UDPARD_ASSERT(rxs->total_payload_size >= rxs->payload_size); - // For single frame transfers, the truncated amount will be 0 (total_payload_size == payload_size) - const size_t truncated_amount = rxs->total_payload_size - rxs->payload_size; - if (CRC_SIZE_BYTES > truncated_amount) - { - UDPARD_ASSERT(out_transfer->payload_size >= (CRC_SIZE_BYTES - truncated_amount)); - out_transfer->payload_size -= CRC_SIZE_BYTES - truncated_amount; - } - - rxs->payload = NULL; // Ownership passed over to the application, nullify to prevent freeing. - } - rxSessionRestart(ins, rxs); // Successful completion. - } - return out; -} - -/// RX session state machine update is the most intricate part of any Cyphal transport implementation. -/// The state model used here is derived from the reference pseudocode given in the original UAVCAN v0 -/// specification. The Cyphal/UDP v1 specification, which this library is an implementation of, does not provide any -/// reference pseudocode. Instead, it takes a higher-level, more abstract approach, where only the high-level -/// requirements are given and the particular algorithms are left to be implementation-defined. Such abstract -/// approach is much advantageous because it allows implementers to choose whatever solution works best for the -/// specific application at hand, while the wire compatibility is still guaranteed by the high-level requirements -/// given in the specification. -UDPARD_PRIVATE int8_t rxSessionUpdate(UdpardInstance* const ins, - UdpardInternalRxSession* const rxs, - const RxFrameModel* const frame, - const uint8_t redundant_transport_index, - const UdpardMicrosecond transfer_id_timeout_usec, - const size_t extent, - UdpardRxTransfer* const out_transfer) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(out_transfer != NULL); - UDPARD_ASSERT(rxs->transfer_id <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT(frame->transfer_id <= UDPARD_TRANSFER_ID_MAX); - - const bool tid_timed_out = (frame->timestamp_usec > rxs->transfer_timestamp_usec) && - ((frame->timestamp_usec - rxs->transfer_timestamp_usec) > transfer_id_timeout_usec); - - const bool not_previous_tid = rxComputeTransferIDDifference(rxs->transfer_id, frame->transfer_id) > 1; - - const bool need_restart = tid_timed_out || ((rxs->redundant_transport_index == redundant_transport_index) && - frame->start_of_transfer && not_previous_tid); - - if (need_restart) - { - rxs->total_payload_size = 0U; - rxs->payload_size = 0U; - rxs->calculated_crc = CRC_INITIAL; - rxs->transfer_id = frame->transfer_id; - rxs->redundant_transport_index = redundant_transport_index; - rxs->last_udp_header_index = 0U; - } - - int8_t out = 0; - if (need_restart && (!frame->start_of_transfer)) - { - rxSessionRestart(ins, rxs); // SOT-miss, no point going further. - } - else - { - // multi-frame transfer - if (!(frame->start_of_transfer && frame->end_of_transfer)) - { - if (frame->end_of_transfer) - { - uint32_t next_expected_frame_index = (1U << UDPARD_END_OF_TRANSFER_OFFSET) + rxs->last_udp_header_index + 1; - if (frame->frame_index != next_expected_frame_index) - { - // Out of order multiframe packet received - out = -UDPARD_ERROR_OUT_OF_ORDER; - // Reset previous frame index to 0 - rxs->last_udp_header_index = 0U; - rxSessionRestart(ins, rxs); - return out; - } - rxs->last_udp_header_index = 0U; - } - else - { - if ((!frame->start_of_transfer && frame->frame_index != rxs->last_udp_header_index + 1) - || (frame->start_of_transfer && frame->frame_index != 0U)) - { - // Out of order multiframe packet received - out = -UDPARD_ERROR_OUT_OF_ORDER; - rxSessionRestart(ins, rxs); - return out; - } - rxs->last_udp_header_index = frame->frame_index; - } - } - const bool correct_transport = (rxs->redundant_transport_index == redundant_transport_index); - const bool correct_tid = (frame->transfer_id == rxs->transfer_id); - if (correct_transport && correct_tid) - { - out = rxSessionAcceptFrame(ins, rxs, frame, extent, out_transfer); - } - } - return out; -} - -UDPARD_PRIVATE int8_t rxAcceptFrame(UdpardInstance* const ins, - UdpardRxSubscription* const subscription, - const RxFrameModel* const frame, - const uint8_t redundant_transport_index, - UdpardRxTransfer* const out_transfer) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(subscription != NULL); - UDPARD_ASSERT(subscription->port_id == frame->port_id); - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(frame->payload != NULL); - UDPARD_ASSERT(frame->transfer_id <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT((UDPARD_NODE_ID_UNSET == frame->destination_node_id) || (ins->node_id == frame->destination_node_id)); - UDPARD_ASSERT(out_transfer != NULL); - - int8_t out = 0; - if ((frame->source_node_id <= UDPARD_NODE_ID_MAX) && (frame->source_node_id != UDPARD_NODE_ID_UNSET)) - { - // If such session does not exist, create it. This only makes sense if this is the first frame of a - // transfer, otherwise, we won't be able to receive the transfer anyway so we don't bother. - if ((NULL == subscription->sessions[frame->source_node_id]) && frame->start_of_transfer) - { - UdpardInternalRxSession* const rxs = - (UdpardInternalRxSession*) ins->memory_allocate(ins, sizeof(UdpardInternalRxSession)); - subscription->sessions[frame->source_node_id] = rxs; - if (rxs != NULL) - { - rxs->transfer_timestamp_usec = frame->timestamp_usec; - rxs->total_payload_size = 0U; - rxs->payload_size = 0U; - rxs->payload = NULL; - rxs->calculated_crc = CRC_INITIAL; - rxs->transfer_id = frame->transfer_id; - rxs->redundant_transport_index = redundant_transport_index; - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - } - // There are two possible reasons why the session may not exist: 1. OOM; 2. SOT-miss. - if (subscription->sessions[frame->source_node_id] != NULL) - { - UDPARD_ASSERT(out == 0); - out = rxSessionUpdate(ins, - subscription->sessions[frame->source_node_id], - frame, - redundant_transport_index, - subscription->transfer_id_timeout_usec, - subscription->extent, - out_transfer); - } - } - else - { - UDPARD_ASSERT(frame->source_node_id == UDPARD_NODE_ID_UNSET); - // Anonymous transfers are stateless. No need to update the state machine, just blindly accept it. - // We have to copy the data into an allocated storage because the API expects it: the lifetime shall be - // independent of the input data and the memory shall be free-able. - const size_t payload_size = - (subscription->extent < frame->payload_size) ? subscription->extent : frame->payload_size; - void* const payload = ins->memory_allocate(ins, payload_size); - if (payload != NULL) - { - rxInitTransferMetadataFromFrame(frame, &out_transfer->metadata); - out_transfer->timestamp_usec = frame->timestamp_usec; - out_transfer->payload_size = payload_size; - out_transfer->payload = payload; - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the - // portability. - (void) memcpy(payload, frame->payload, payload_size); // NOLINT - out = 1; - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - } - return out; -} - -UDPARD_PRIVATE int8_t -rxSubscriptionPredicateOnPortID(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) -{ - const UdpardPortID sought = *((const UdpardPortID*) user_reference); - const UdpardPortID other = ((const UdpardRxSubscription*) node)->port_id; - static const int8_t NegPos[2] = {-1, +1}; - // Clang-Tidy mistakenly identifies a narrowing cast to int8_t here, which is incorrect. - return (sought == other) ? 0 : NegPos[sought > other]; // NOLINT no narrowing conversion is taking place here -} - -UDPARD_PRIVATE int8_t -rxSubscriptionPredicateOnStruct(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) -{ - return rxSubscriptionPredicateOnPortID(&((UdpardRxSubscription*) user_reference)->port_id, node); -} - -// --------------------------------------------- PUBLIC API --------------------------------------------- - -UdpardInstance udpardInit(const UdpardMemoryAllocate memory_allocate, const UdpardMemoryFree memory_free) -{ - UDPARD_ASSERT(memory_allocate != NULL); - UDPARD_ASSERT(memory_free != NULL); - const UdpardInstance out = { - .user_reference = NULL, - .node_id = UDPARD_NODE_ID_UNSET, - .memory_allocate = memory_allocate, - .memory_free = memory_free, - .rx_subscriptions = {NULL, NULL, NULL}, - }; - return out; -} - -UdpardTxQueue udpardTxInit(const size_t capacity, const size_t mtu_bytes) -{ - UdpardTxQueue out = { - .capacity = capacity, - .mtu_bytes = mtu_bytes, - .size = 0, - .root = NULL, - .user_reference = NULL, - }; - return out; -} - -int32_t udpardTxPush(UdpardTxQueue* const que, - UdpardInstance* const ins, - const UdpardMicrosecond tx_deadline_usec, - const UdpardTransferMetadata* const metadata, - const size_t payload_size, - const void* const payload) -{ - int32_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - if ((ins != NULL) && (que != NULL) && (metadata != NULL) && ((payload != NULL) || (0U == payload_size))) - { - const size_t pl_mtu = adjustPresentationLayerMTU(que->mtu_bytes); - UdpardSessionSpecifier specifier; - const int32_t specifier_result = txMakeSessionSpecifier(metadata, ins->node_id, ins->local_ip_addr, &specifier); - if (specifier_result >= 0) - { - if (payload_size + CRC_SIZE_BYTES <= pl_mtu - sizeof(UdpardFrameHeader)) - { - out = txPushSingleFrame(que, - ins, - tx_deadline_usec, - &specifier, - ins->node_id, - metadata->remote_node_id, - metadata->port_id, - metadata->transfer_kind, - metadata->priority, - metadata->transfer_id, - payload_size, - payload); - UDPARD_ASSERT((out < 0) || (out == 1)); - } - else - { - out = txPushMultiFrame(que, - ins, - pl_mtu, - tx_deadline_usec, - &specifier, - ins->node_id, - metadata->remote_node_id, - metadata->port_id, - metadata->transfer_kind, - metadata->priority, - metadata->transfer_id, - payload_size, - payload); - UDPARD_ASSERT((out < 0) || (out >= 2)); - } - } - else - { - out = specifier_result; - } - } - UDPARD_ASSERT(out != 0); - return out; -} - -const UdpardTxQueueItem* udpardTxPeek(const UdpardTxQueue* const que) -{ - const UdpardTxQueueItem* out = NULL; - if (que != NULL) - { - // Paragraph 6.7.2.1.15 of the C standard says: - // A pointer to a structure object, suitably converted, points to its initial member, and vice versa. - out = (const UdpardTxQueueItem*) cavlFindExtremum(que->root, false); - } - return out; -} - -UdpardTxQueueItem* udpardTxPop(UdpardTxQueue* const que, const UdpardTxQueueItem* const item) -{ - UdpardTxQueueItem* out = NULL; - if ((que != NULL) && (item != NULL)) - { - // Intentional violation of MISRA: casting away const qualifier. This is considered safe because the API - // contract dictates that the pointer shall point to a mutable entity in RAM previously allocated by the - // memory manager. It is difficult to avoid this cast in this context. - out = (UdpardTxQueueItem*) item; // NOSONAR casting away const qualifier. - // Paragraph 6.7.2.1.15 of the C standard says: - // A pointer to a structure object, suitably converted, points to its initial member, and vice versa. - // Note that the highest-priority frame is always a leaf node in the AVL tree, which means that it is very - // cheap to remove. - cavlRemove(&que->root, &item->base); - que->size--; - } - return out; -} - -int8_t udpardRxAccept(UdpardInstance* const ins, - const UdpardMicrosecond timestamp_usec, - UdpardFrame* const frame, - const uint8_t redundant_transport_index, - UdpardRxTransfer* const out_transfer, - UdpardRxSubscription** const out_subscription) -{ - int8_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - if ((ins != NULL) && (out_transfer != NULL) && (frame != NULL) && - ((frame->payload != NULL) || (sizeof(frame->udp_cyphal_header) == frame->payload_size))) - { - RxFrameModel model = {0}; - if (rxTryParseFrame(timestamp_usec, frame, &model)) - { - if ((UDPARD_NODE_ID_UNSET == model.destination_node_id) || (ins->node_id == model.destination_node_id)) - { - // This is the reason the function has a logarithmic time complexity of the number of subscriptions. - // Note also that this one of the two variable-complexity operations in the RX pipeline; the other - // one is memcpy(). Excepting these two cases, the entire RX pipeline contains neither loops nor - // recursion. - UdpardRxSubscription* const sub = - (UdpardRxSubscription*) cavlSearch(&ins->rx_subscriptions[(size_t) model.transfer_kind], - &model.port_id, - &rxSubscriptionPredicateOnPortID, - NULL); - if (out_subscription != NULL) - { - *out_subscription = sub; // Expose selected instance to the caller. - } - if (sub != NULL) - { - UDPARD_ASSERT(sub->port_id == model.port_id); - out = rxAcceptFrame(ins, sub, &model, redundant_transport_index, out_transfer); - } - else - { - out = 0; // No matching subscription. - } - } - else - { - out = 0; // Mis-addressed frame (normally it should be filtered out by the hardware). - } - } - else - { - out = 0; // A non-Cyphal/UDP input frame. - } - } - UDPARD_ASSERT(out <= 1); - return out; -} - -/// DONE -> This shouldn't change from canard to udpard -int8_t udpardRxSubscribe(UdpardInstance* const ins, - const UdpardTransferKind transfer_kind, - const UdpardPortID port_id, - const size_t extent, - const UdpardMicrosecond transfer_id_timeout_usec, - UdpardRxSubscription* const out_subscription) -{ - int8_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - const size_t tk = (size_t) transfer_kind; - if ((ins != NULL) && (out_subscription != NULL) && (tk < UDPARD_NUM_TRANSFER_KINDS)) - { - // Reset to the initial state. This is absolutely critical because the new payload size limit may be larger - // than the old value; if there are any payload buffers allocated, we may overrun them because they are - // shorter than the new payload limit. So we clear the subscription and thus ensure that no overrun may - // occur. - out = udpardRxUnsubscribe(ins, transfer_kind, port_id); - if (out >= 0) - { - out_subscription->transfer_id_timeout_usec = transfer_id_timeout_usec; - out_subscription->extent = extent; - out_subscription->port_id = port_id; - for (size_t i = 0; i < RX_SESSIONS_PER_SUBSCRIPTION; i++) - { - // The sessions will be created ad-hoc. Normally, for a low-jitter deterministic system, - // we could have pre-allocated sessions here, but that requires too much memory to be feasible. - // We could accept an extra argument that would instruct us to pre-allocate sessions here? - out_subscription->sessions[i] = NULL; - } - const UdpardTreeNode* const res = cavlSearch(&ins->rx_subscriptions[tk], - out_subscription, - &rxSubscriptionPredicateOnStruct, - &avlTrivialFactory); - (void) res; - UDPARD_ASSERT(res == &out_subscription->base); - out = (out > 0) ? 0 : 1; - } - } - return out; -} - -/// DONE -> This shouldn't change from canard to udpard -int8_t udpardRxUnsubscribe(UdpardInstance* const ins, - const UdpardTransferKind transfer_kind, - const UdpardPortID port_id) -{ - int8_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - const size_t tk = (size_t) transfer_kind; - if ((ins != NULL) && (tk < UDPARD_NUM_TRANSFER_KINDS)) - { - UdpardPortID port_id_mutable = port_id; - UdpardRxSubscription* const sub = (UdpardRxSubscription*) - cavlSearch(&ins->rx_subscriptions[tk], &port_id_mutable, &rxSubscriptionPredicateOnPortID, NULL); - if (sub != NULL) - { - cavlRemove(&ins->rx_subscriptions[tk], &sub->base); - UDPARD_ASSERT(sub->port_id == port_id); - out = 1; - for (size_t i = 0; i < RX_SESSIONS_PER_SUBSCRIPTION; i++) - { - ins->memory_free(ins, (sub->sessions[i] != NULL) ? sub->sessions[i]->payload : NULL); - ins->memory_free(ins, sub->sessions[i]); - sub->sessions[i] = NULL; - } - } - else - { - out = 0; - } - } - return out; -} diff --git a/tests/.clang-tidy b/tests/.clang-tidy index 78a09ef..85da250 100644 --- a/tests/.clang-tidy +++ b/tests/.clang-tidy @@ -36,6 +36,7 @@ Checks: >- -*-no-malloc, -cert-msc30-c, -cert-msc50-cpp, + -modernize-macro-to-enum, WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' AnalyzeTemporaryDtors: false diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0321aa8..52b0495 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,6 @@ # This software is distributed under the terms of the MIT License. # Copyright (c) 2016 OpenCyphal. # Author: Pavel Kirienko -# Contributors: https://github.com/OpenCyphal/libudpard/contributors. cmake_minimum_required(VERSION 3.12) project(udpard_tests C CXX) @@ -10,6 +9,8 @@ enable_testing() set(CTEST_OUTPUT_ON_FAILURE ON) set(library_dir "${CMAKE_SOURCE_DIR}/../libudpard") +set(NO_STATIC_ANALYSIS OFF CACHE BOOL "disable udpard static analysis") + # Use -DNO_STATIC_ANALYSIS=1 to suppress static analysis. # If not suppressed, the tools used here shall be available, otherwise the build will fail. if (NOT NO_STATIC_ANALYSIS) @@ -36,16 +37,15 @@ endif () set(CMAKE_CXX_STANDARD 17) set(CXX_EXTENSIONS OFF) add_compile_options( - -Wall -Wextra -pedantic -fstrict-aliasing -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef - -Werror + -Wall -Wextra -Werror -pedantic -fstrict-aliasing -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef -Wconversion -Wtype-limits -Wsign-conversion -Wcast-align ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual -Wnon-virtual-dtor -Wsign-promo") -include_directories(catch ${library_dir}) -add_definitions(-DCATCH_CONFIG_FAST_COMPILE=1 -DCATCH_CONFIG_ENABLE_ALL_STRINGMAKERS=1) +include_directories(${library_dir}) +include_directories(SYSTEM catch) -set(common_sources catch/main.cpp ${library_dir}/udpard.c) +set(common_sources ${CMAKE_SOURCE_DIR}/main.cpp ${library_dir}/udpard.c) function(gen_test name files compile_definitions compile_flags link_flags c_standard) add_executable(${name} ${common_sources} ${files}) @@ -61,11 +61,13 @@ function(gen_test name files compile_definitions compile_flags link_flags c_stan add_test("run_${name}" "${name}" --rng-seed time) endfunction() -function(gen_test_matrix name files compile_definitions compile_flags) - gen_test("${name}_x64_c99" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64" "99") - # gen_test("${name}_x32_c99" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32" "99") - gen_test("${name}_x64_c11" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64" "11") - # gen_test("${name}_x32_c11" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32" "11") +function(gen_test_matrix name files compile_definitions compile_flags link_flags) + gen_test("${name}_x64_c99" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64 ${link_flags}" "99") + gen_test("${name}_x32_c99" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32 ${link_flags}" "99") + target_link_libraries("${name}_x32_c99" ${ADDITIONAL_LIBS_32}) + gen_test("${name}_x64_c11" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64 ${link_flags}" "11") + gen_test("${name}_x32_c11" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32 ${link_flags}" "11") + target_link_libraries("${name}_x32_c11" ${ADDITIONAL_LIBS_32}) # Coverage is only available for GCC builds. if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_BUILD_TYPE STREQUAL "Debug")) gen_test("${name}_cov" @@ -79,11 +81,13 @@ endfunction() # Disable missing declaration warning to allow exposure of private definitions. gen_test_matrix(test_private - "test_private_crc.cpp;test_private_rx.cpp;test_private_tx.cpp;test_private_cavl.cpp;" + "test_private_cavl.cpp;" "-DUDPARD_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/udpard_config_private.h\"" - "-Wno-missing-declarations") + "-Wno-missing-declarations" + "") gen_test_matrix(test_public - "test_public_tx.cpp;test_public_rx.cpp;test_public_roundtrip.cpp;test_self.cpp;" + "test_self.cpp" "" - "-Wmissing-declarations") + "-Wmissing-declarations" + "") diff --git a/tests/catch/catch.hpp b/tests/catch/catch.hpp index b852ddb..9b309bd 100644 --- a/tests/catch/catch.hpp +++ b/tests/catch/catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.0.1 - * Generated: 2017-11-03 11:53:39.642003 + * Catch v2.13.10 + * Generated: 2022-10-16 11:01:23.452308 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2017 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -13,6 +13,10 @@ // start catch.hpp +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 10 + #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ @@ -26,50 +30,62 @@ # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC -# pragma clang diagnostic ignored "-Wglobal-constructors" -# pragma clang diagnostic ignored "-Wvariadic-macros" -# pragma clang diagnostic ignored "-Wc99-extensions" -# pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ -# pragma GCC diagnostic ignored "-Wvariadic-macros" -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wparentheses" + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details # pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) # define CATCH_CONFIG_EXTERNAL_INTERFACES # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif #endif +#if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include -# if TARGET_OS_MAC == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS #endif // end catch_platform.h + #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED @@ -77,6 +93,13 @@ # endif #endif +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h // start catch_tag_alias_autoregistrar.h // start catch_common.h @@ -89,6 +112,7 @@ // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too @@ -101,37 +125,74 @@ #ifdef __cplusplus -# if __cplusplus >= 201402L +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) # define CATCH_CPP14_OR_GREATER # endif +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + #endif -#ifdef __clang__ +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# endif +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ @@ -139,6 +200,25 @@ # define CATCH_CONFIG_COLOUR_NONE #endif +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ @@ -146,12 +226,19 @@ // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ -#ifdef _MSC_VER +#if defined(_MSC_VER) // Universal Windows platform does not support SEH // Or console colours (or console at all...) @@ -161,8 +248,45 @@ # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif +# if !defined(__clang__) // Handle Clang masquerading for msvc + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL + +// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + //////////////////////////////////////////////////////////////////////////////// // Use of __COUNTER__ is suppressed during code analysis in @@ -174,24 +298,170 @@ #define CATCH_INTERNAL_CONFIG_COUNTER #endif +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif // end catch_compiler_capabilities.h @@ -207,6 +477,10 @@ #include #include +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + namespace Catch { struct CaseSensitive { enum Choice { @@ -228,14 +502,17 @@ namespace Catch { struct SourceLineInfo { SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - bool empty() const noexcept; + bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; @@ -245,10 +522,10 @@ namespace Catch { std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - // This is just here to avoid compiler warnings with macro constants and boolean literals - bool isTrue( bool value ); - bool alwaysTrue(); - bool alwaysFalse(); + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; // Use this in variadic streaming macros to allow // >> +StreamEndStop @@ -275,7 +552,11 @@ namespace Catch { } // end namespace Catch -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h @@ -283,7 +564,6 @@ namespace Catch { // start catch_interfaces_testcase.h #include -#include namespace Catch { @@ -294,8 +574,6 @@ namespace Catch { virtual ~ITestInvoker(); }; - using ITestCasePtr = std::shared_ptr; - class TestCase; struct IConfig; @@ -305,6 +583,7 @@ namespace Catch { virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); @@ -317,74 +596,366 @@ namespace Catch { #include #include #include +#include namespace Catch { - class StringData; - /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. c_str() must return a null terminated - /// string, however, and so the StringRef will internally take ownership - /// (taking a copy), if necessary. In theory this ownership is not externally - /// visible - but it does mean (substring) StringRefs should not be shared between - /// threads. + /// it may not be null terminated. class StringRef { - friend struct StringRefTestAccess; - + public: using size_type = std::size_t; + using const_iterator = const char*; - char const* m_start; - size_type m_size; + private: + static constexpr char const* const s_empty = ""; - char* m_data = nullptr; + char const* m_start = s_empty; + size_type m_size = 0; - void takeOwnership(); + public: // construction + constexpr StringRef() noexcept = default; - public: // construction/ assignment - StringRef() noexcept; - StringRef( StringRef const& other ) noexcept; - StringRef( StringRef&& other ) noexcept; StringRef( char const* rawChars ) noexcept; - StringRef( char const* rawChars, size_type size ) noexcept; - StringRef( std::string const& stdString ) noexcept; - ~StringRef() noexcept; - auto operator = ( StringRef other ) noexcept -> StringRef&; - operator std::string() const; + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} - void swap( StringRef& other ) noexcept; + explicit operator std::string() const { + return std::string(m_start, m_size); + } public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } - auto operator[] ( size_type index ) const noexcept -> char; + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } public: // named queries - auto empty() const noexcept -> bool; - auto size() const noexcept -> size_type; - auto numberOfCharacters() const noexcept -> size_type; + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches - auto substr( size_type start, size_type size ) const noexcept -> StringRef; + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; - private: // ownership queries - may not be consistent between calls - auto isOwned() const noexcept -> bool; - auto isSubstring() const noexcept -> bool; + // Returns the current start pointer. May not be null-terminated. auto data() const noexcept -> char const*; - }; - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; - auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } } // namespace Catch +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + // end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template