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

Add LibUDPard demo #16

Merged
merged 36 commits into from Sep 15, 2023
Merged

Add LibUDPard demo #16

merged 36 commits into from Sep 15, 2023

Conversation

pavel-kirienko
Copy link
Member

The code is a little sloppy compared to the library itself but it should be adequate for the demo.

I moved the platform-specific parts -- the UDP stack and the storage I/O -- into separate modules to make the API surface clearly visible. This also simplifies porting if one wants to run this demo on an MCU.

I avoided building strong abstractions on top of LibUDPard because that may obscure the API of the library and attract attention of the reader to the features of the demo instead of those of the library itself. The flipside is that main.c has to be fairly large.

The Cavl library was added by copying the header file instead of adding the submodule because the entire library is just a single file alone so the submodule approach seemed like an overkill. We can change this if necessary, though. See a related question here: OpenCyphal/libudpard#11

@thirtytwobits
Copy link

👀

Copy link

@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.

Will continue this review tomorrow but note that I get integer narrowing warnings from libudpard when building on 64-bit systems. Have you tried building on linux 64?

_Alignas(max_align_t) static uint_least8_t _name##_pool[(_block_size_bytes) * (_block_count)]; \
struct MemoryBlockAllocator _name = memoryBlockInit(sizeof(_name##_pool), &_name##_pool[0], (_block_size_bytes))

/// The user shall not attempt to change any of the fields manually.

Choose a reason for hiding this comment

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

OH? Is that a challenge? Perhaps I will change these, thank-you-very-much!!!

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 was going for the record for the smallest possible implementation of a block pool allocator. Counting 101 LoC at the moment.

libudpard/CMakeLists.txt Outdated Show resolved Hide resolved
@lydia-at-amazon
Copy link

Very exciting to see! Will review more thoroughly tomorrow

libudpard_demo/src/udp.c Outdated Show resolved Hide resolved
libudpard_demo/src/main.c Outdated Show resolved Hide resolved
libudpard_demo/src/main.c Show resolved Hide resolved
libudpard_demo/src/storage.c Show resolved Hide resolved
libudpard_demo/src/main.c Outdated Show resolved Hide resolved
libudpard_demo/src/storage.h Show resolved Hide resolved
ok = ok && (setsockopt(self->fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == 0);
#endif
// Binding to the multicast group address is necessary on GNU/Linux: https://habr.com/ru/post/141021/
// Binding to a multicast address is not allowed on Windows, and it is not necessary there;

Choose a reason for hiding this comment

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

So...this will fail on Windows?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this will fail on Windows. To make it work there, the binding has to be conditional. I could throw in some ifdefs in there but we don't really want to test it against Windows, do we?

Choose a reason for hiding this comment

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

It'd be nice if this worked on Windows, yes.

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 added the required preprocessor check but this is not yet tested on Windows. I'm afraid that ensuring compatibility with Windows is a rather significant chunk of work.

Comment on lines +9 to +10
/// Enable SO_REUSEPORT.
#define _DEFAULT_SOURCE // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)

Choose a reason for hiding this comment

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

Does the SO_REUSEPORT comment relate to defining DEFAULT_SOURCE?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. _DEFAULT_SOURCE is a feature test macro that enables certain non-POSIX extensions that are disabled by default when a strict standard mode is used; in this case, -std=c99. See https://man7.org/linux/man-pages/man7/feature_test_macros.7.html

int16_t res = -EINVAL;
if ((self != NULL) && (local_iface_address > 0))
{
self->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Choose a reason for hiding this comment

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

Should we use SOCK_NONBLOCK? Guess that's more up to customers if they want to use that or not

Copy link
Member Author

Choose a reason for hiding this comment

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

On line 48, we have fcntl(self->fd, F_SETFL, O_NONBLOCK). We could move it here as well if desired.

}
}

static void cbOnNodeIDAllocationData(struct Subscriber* const self, struct UdpardRxTransfer* const transfer)

Choose a reason for hiding this comment

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

What does cb stand for?

Choose a reason for hiding this comment

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

Oh it stands for callback

libudpard_demo/src/main.c Outdated Show resolved Hide resolved
libudpard_demo/src/main.c Outdated Show resolved Hide resolved
libudpard_demo/src/main.c Outdated Show resolved Hide resolved
}

// Process the RX sockets that became readable.
// The time has to be re-sampled because the blocking wait may have taken a long time.

Choose a reason for hiding this comment

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

What's the benefit of having the blocking wait?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's just standard IO multiplexing. We suspend the thread when there's nothing to do. It is unblocked either when there is a socket to handle or the application needs to do something.

continue;
}
// Pass the data buffer into LibUDPard for further processing. It takes ownership of the buffer.
if (rx_aw[i].user_reference != NULL) // This is used to differentiate subscription sockets from RPC sockets.

Choose a reason for hiding this comment

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

Is there not a better way to differentiate between messages and services than checking whether the user reference is NULL or not?

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 ask myself the same question. We need to keep state per awaitable to differentiate between the two. We can't subtype it to add custom state because that breaks the array layout. All of the better solutions I could come up with are various shades of bad:

  1. Pass awaitables as an array of pointers -- requires an extra array.
  2. Use a linked list -- results in a clumsy API.
  3. Add the required fields to the awaitable type -- breaks encapsulation.

I guess I better add an explanatory comment about this.

} // Otherwise, it's a request from another allocation client node, or we already have a node-ID.
}

static void cbOnMyData(struct Subscriber* const self, struct UdpardRxTransfer* const transfer)

Choose a reason for hiding this comment

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

You could use the same callback for multiple subscribers, correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

ye

/// `____/ .___/`___/_/ /_/`____/`__, / .___/_/ /_/`__,_/_/
/// /_/ /____/_/
///
/// In Cyphal, registers are named values that can be read and possibly written over the network. They are used

Choose a reason for hiding this comment

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

If everything is configured statically, are registers still needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, they are quite optional. The only required thing is the heartbeat publisher.

@lydia-at-amazon
Copy link

lydia-at-amazon commented Sep 15, 2023

General feeling is that main is a little complex but that's why we're going to provide Libcyphal as well to help abstract a lot of the setup required to use libudpard from the user.

@pavel-kirienko pavel-kirienko merged commit 961171b into main Sep 15, 2023
@pavel-kirienko pavel-kirienko deleted the libudpard branch September 15, 2023 17:10
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.

None yet

3 participants