Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transmission pipeline and cosmetic adjustments to the API #34

Merged
merged 31 commits into from
Jul 10, 2023

Conversation

pavel-kirienko
Copy link
Member

Please focus on udpard.c and udpard.h. Upon a closer inspection it appeared to be easier to produce a completely new implementation that exploits the design features of Cyphal/UDP instead of adapting the original CAN implementation.

The code is fully covered by tests; Sonar reports a slightly lower value because of imperfections of llvm-cov.

Golden tests were produced with the help of PyCyphal; relevant Python snippets are provided in the comments.

The commit history is uninformative and should not be looked at.

@pavel-kirienko pavel-kirienko self-assigned this Jul 1, 2023
@pavel-kirienko pavel-kirienko marked this pull request as ready for review July 2, 2023 12:59
tests/CMakeLists.txt Outdated Show resolved Hide resolved
libudpard/udpard.h Show resolved Hide resolved
libudpard/udpard.h Outdated Show resolved Hide resolved
tests/helpers.hpp Outdated Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
Copy link
Member

@thirtytwobits thirtytwobits left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming we don't have the work in here to handle a low-priority, long-timeout item in a tx queue?

#define SUBJECT_MULTICAST_GROUP_ADDRESS_MASK 0xEF000000UL
#define SERVICE_MULTICAST_GROUP_ADDRESS_MASK 0xEF010000UL

UDPARD_PRIVATE uint32_t makeSubjectIPGroupAddress(const UdpardPortID subject_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think we should switch. I don't mind having a bunch of individual test executables. It's how I structured CETL even without the need to test cpp internals.

libudpard/udpard.c Show resolved Hide resolved
libudpard/udpard.c Outdated Show resolved Hide resolved
libudpard/udpard.c Show resolved Hide resolved
@pavel-kirienko
Copy link
Member Author

I'm assuming we don't have the work in here to handle a low-priority, long-timeout item in a tx queue?

No, I considered this to not be part of what we defined as "significant completion". This will be taken care of after the core functionality is in place: #33

I do think we should switch. I don't mind having a bunch of individual test executables. It's how I structured CETL even without the need to test cpp internals.

Okay, I just did that and discovered (too late) that it won't work. The problem is that it is difficult to compile a C file using a C++ compiler without suppressing a very large set of sensible compiler warnings. Clang-Tidy and Sonar can also be made to not emit diagnostics for the included C file but it it slightly less trivial than I would like it to be. As C and C++ are, after all, very different languages, I'm worried that by compiling C with a C++ compiler we may create maintenance issues for us in the future, particularly if we decide to switch to a newer version of either standard at one point. You can see what it looks like in the second-to-last commit (the next one reverts it).

I suggest keeping the existing design as-is. The presence of exposed.hpp pains me but it is less undesirable than the alternative.

@thirtytwobits
Copy link
Member

thirtytwobits commented Jul 6, 2023

Okay, I just did that and discovered (too late) that it won't work. The problem is that it is difficult to compile a C file using a C++ compiler without suppressing a very large set of sensible compiler warnings...

import the .c into an exposed.c that defines test_hooks. for example:

// realcode.c
static int foo(const char* p)
{
 ...
}
// exposed.c
#include "realcode.c"

EXPORT int exposed_foo(const char* p)
{
    return foo(p);
}

@pavel-kirienko
Copy link
Member Author

Could you please elaborate on the value of this approach compared to exposed.hpp? I see one in that there will be no need to customize UDPARD_INTERNAL but it doesn't seem significant; plus it doesn't obviate the need for exposed.hpp.

@thirtytwobits
Copy link
Member

thirtytwobits commented Jul 6, 2023

Could you please elaborate on the value of this approach compared to exposed.hpp? I see one in that there will be no need to customize UDPARD_INTERNAL but it doesn't seem significant; plus it doesn't obviate the need for exposed.hpp.

It avoids polluting the production code with a test macro or any knowledge of the tests. I can accept any crazy plan needed in unit tests but not in the product.

Copy link
Collaborator

@lydia-at-amazon lydia-at-amazon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, all my comments are just nits.


/// The port number is defined in the Cyphal/UDP Specification. The same port number is used for all transfer kinds.
#define UDPARD_UDP_PORT 9382U
#define UDPARD_MTU_DEFAULT_MAX_SINGLE_FRAME (UDPARD_MTU_MAX - 4U)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably just blind, but where is UDPARD_MTU_MAX defined? Should this be UDPARD_MTU_DEFAULT - 4U?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, this went unnoticed because there was no test for this. There is now, it's called testMakeChainSingleFrameDefaultMTU and it ensures that UDPARD_MTU_DEFAULT_MAX_SINGLE_FRAME-sized payload fits into a UDPARD_MTU_DEFAULT-sized frame, and a spillover occurs when the payload is enlarged by 1 byte.

/// See UDPARD_MTU_*.
/// The value can be changed arbitrarily at any time between enqueue operations.
size_t mtu_bytes;
/// The value is constrained by the library to be positive.
size_t mtu;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not mtu_bytes? Would prefer to keep the units

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no strong preference either way but the constants do not mention bytes and MTU is in bytes (octets) by definition. We can change it back if needed.

break;
}
const bool last = (payload_size_with_crc - offset) <= mtu;
byte_t* write_ptr = txSerializeHeader(&tqi->payload_buffer[0], meta, (uint32_t) out.count, last);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're using out.count as the frame index, could you make the count member of TxChain a uint32_t so you don't have to cast here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. count is used to represent the number of items allocated in memory without relation to the design of the protocol header (that part belongs to a different scope), so I assumed it is reasonable to use size_t here to avoid scope leakage.
  2. Usage of uint32_t requires a cast at tx->queue_size += chain.count in narrow-word platforms like AVR (caught by the CI).

UdpardPriority priority; ///< Do we need this exposed in the public structure? We already have DSCP there.
// The MISRA violation here is hard to get rid of without having to allocate a separate memory block for the
// payload, which is much more costly risk-wise.
byte_t payload_buffer[]; // NOSONAR MISRA C 18.7 Flexible array member.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you choosing not to make the payload buffer the size of UDPARD_MTU_DEFAULT to save on memory?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not the main reason. The main reason is to support jumbo frames with MTU >> 1500 bytes.

const uint32_t frame_index,
const bool end_of_transfer)
{
byte_t* p = destination_buffer;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more descriptive variable name we could use instead of p or this common practice to refer to a generic pointer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think p is common, but on the other hand, some style guides prohibit single-word names except i,j,k, reserved for loop counters, so I changed it to ptr.

user_transfer_reference);
if (NULL == out.head)
{
out.head = tqi;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does tqi stand for? tx_queue_item? We have a style guideline that I like about not abbreviating identifiers by removing letters since it helps with readability.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed as item.

// =====================================================================================================================

/// This is a subclass of UdpardTxItem. A pointer to this type can be cast to UdpardTxItem safely.
/// This is standard-compliant. The paragraph 6.7.2.1.15 says:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which standard? The C standard?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

tests/test_private_tx.cpp Outdated Show resolved Hide resolved
return p;
}

static inline byte_t* txSerializeHeader(byte_t* const destination_buffer,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function would be more readable if we had a struct for the frame header like we did before:
https://github.com/OpenCyphal-Garage/libudpard/blob/main/libudpard/udpard.h#L133
https://github.com/OpenCyphal-Garage/libudpard/blob/main/libudpard/udpard.c#L325

And then populated the fields of the struct rather than using pointer arithmetic.

Is there a reason this new implementation moved away from that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The old approach is endian-sensitive.
  2. I think that TransferMetadata offers a stronger and more useful abstraction because it encapsulates all transfer-specific metadata in a single entity that is independent of values that vary within the transfer (the frame index and the EOR flag), which allows passing it unmodified from the top-level function to the header serializer.

@sonarcloud
Copy link

sonarcloud bot commented Jul 8, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

96.3% 96.3% Coverage
0.0% 0.0% Duplication

@aentinger
Copy link

Nudging @thirtytwobits to proceed with the review because I'm looking into integrating libudpard - once ready - into 107-Arduino-Cyphal 😁

@thirtytwobits
Copy link
Member

👀

@pavel-kirienko pavel-kirienko merged commit 5727576 into main2 Jul 10, 2023
24 checks passed
@pavel-kirienko pavel-kirienko deleted the pavel branch July 10, 2023 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants