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

Possibility to use libcoap with an external event loop #1554

Closed
mkicherer opened this issue Dec 12, 2024 · 13 comments
Closed

Possibility to use libcoap with an external event loop #1554

mkicherer opened this issue Dec 12, 2024 · 13 comments

Comments

@mkicherer
Copy link

Hello,

as many CoAP implementations seem to have ceased development 2-3 years ago, I am experimenting with integrating libcoap in other programming languages (Python via ctypes, C# via PInvoke and JS). I was able to get examples running in these cases but had to add workarounds. One workaround was necessary because I need to integrate libcoap into an existing event loop as epoll and select() are not (fully) available.

I saw that I can call coap_io_prepare_io to get a list of coap_socket_t that contain the FDs and the event types. Unfortunately, this struct is in an internal header. I copied the struct to my code for this workaround and I was able to use libcoap with the other event loop. My question is, is there an officially supported way to integrate libcoap in a separate event loop that I missed and if not, would it be possible to make coap_socket_t public or add access function to the fd and flags properties?

Btw., do you have an idea why many lost interest in CoAP? In my environment many people are using MQTT. I see the advantage that most of the complexity is in the broker but for my use cases I want direct communication and CoAP also offers other benefits.

Thank you!

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 12, 2024

It is good that you are trying to provide libcoap support within other programming environments.

I think all that you need to do is call coap_io_process(ctx, COAP_IO_NO_WAIT); from within your external event loop whenever your external event loop is triggered.

(coap_io_process() calls coap_io_prepare_io())

This will then also handle any packet re-transmits etc.

Btw., do you have an idea why many lost interest in CoAP?

Good question that I do not have an answer for. I don't think that CoAP is going to go away though.

@mkicherer
Copy link
Author

I also call this function periodically in some cases but that is something I would like to avoid. In case of a control application for a physical device, I think the function would be called unnecessarily most of the time as nothing changes. So I would set a comparatively high cycle time (maybe 100+x ms). On the other hand, this might slow down file transfers considerably.

I guess adding access functions for these two struct properties would be the least invasive change (only read for fd and read&write for flags). Would you accept such a PR? If yes, I saw that the fd property is not present on all platforms. Could the corresponding access function simply return zero if the property does not exist?

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 13, 2024

So I would set a comparatively high cycle time (maybe 100+x ms). On the other hand, this might slow down file
transfers considerably.

coap_io_process() will not wait for the full timeout specified if an input is received, or an internal libcoap timeout has occurred (i.e. for a packet re-transmission). So providing it is repeatably called, this should not slow down blocked transfers, but I guess it is possible.

I guess adding access functions for these two struct properties would be the least invasive change (only read for fd
and read&write for flags). Would you accept such a PR?

I don't see why not if it makes sense.

However, to not to have to change the flags, I would make fd_ready_cb(() invoke coap_io_process(elf.lcoap_ctx, COAP_IO_NO_WAIT) rather than coap_io_do_io(self.lcoap_ctx, now) so you do not have to track any underlying libcoap changes (or get epoll confusion) for the flags..

I saw that the fd property is not present on all platforms.

One of the reasons why structures are kept opaque is because of different build type sizes. In this case, I would return COAP_INVALID_SOCKET as 0 is a valid file descriptor (it also possible a socket is in a closed state where fd == COAP_INVALID_SOCKET). However, not having a file descriptor will mess up asyncio.

@mkicherer
Copy link
Author

Thank you!

@mkicherer
Copy link
Author

Sorry, I have to reopen this as I accidentally missed the following issues:

The COAP_SOCKET_* flags are still internal. I guess I can simply move them from internal to the public header.

However, to use coap_io_prepare_io I have to know the size of coap_socket_t. I thought I can just add a helper function that calls coap_io_prepare_io with an increasing number of structs until num_sockets is smaller than max_sockets similar to what I did in libcoapy. I saw that you usually do not call malloc directly but coap_malloc_type with an additional type parameter. Here I stopped to ask if this is a viable way to solve this or if I should just create a coap_socket_alloc_new function and use this to prepare a list of pointers to call coap_io_prepare_io?

Thank you!

@mkicherer mkicherer reopened this Dec 13, 2024
@mkicherer
Copy link
Author

Hm, I think I got it wrong. Please ignore the second part for now. I have to think about it again next week with a fresh mind.

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 13, 2024

I think I want to tackle this from a different direction.

coap_io_process() calls coap_io_prepare_io() and coap_io_do_io(), and when called with COAP_IO_NO_WAIT does not wait on the internal libcoap select(). Just use coap_io_process() in your fd_ready_cb(), but still invoke fd_callback() to get a fresh state of fds for asyncio.

So, if you have a new function (e.g. coap_io_get_fd_list()) that returns 2 sets of (internally used by libcoap) fds - one for read and the other for write, and use this in your fd_callback() instead of coap_io_prepare_io(), you can then just do your {add|remove}_{reader|writer} as appropriate.

[Trying to shoehorn in coap_io_prepare_io() is getting more problematic!]

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 14, 2024

Please see #1556 which should address your requirements.

I am inclined to revert out #1555. In particular, coap_socket_set_flags() has the potential to mess up the libcoap file descriptor state machine.

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 16, 2024

#1556 is not complete - need a timeout mechanism for the non epoll case. This is needed for packet retransmission etc.

I could use the timerfd_*() functions (which epoll uses) to add in a timerfd fd to the list of returned fds if that works for you.

@mkicherer
Copy link
Author

Good morning,

in this particular case, could I use #1556 and call coap_io_prepare_io with max_sockets=0 just to get the timeout? On Windows, there is no equivalent to timerfd, as far as I know. On my other platforms, I can use epoll in all cases.

I will try #1556 now. Thank you!

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 16, 2024

in this particular case, could I use #1556 and call coap_io_prepare_io
with max_sockets=0

Sure - that should work.

@mrdeep1
Copy link
Collaborator

mrdeep1 commented Dec 16, 2024

I have just added to coap_io_get_fds() in #1556 a new parameter - rem_timeout_ms, so no need to call coap_io_prepare_io() with all its overheads. rem_timeout_ms can be used for your timeout.

So, fd_ready_cb() should just need to call coap_io_process() and fd_callback() to get a fresh state of fds for asyncio along with the timeout in milli-secs to use (if 0, there is no timeout - just waiting for the next change in fd state to trigger fd_ready_cb(),

@mkicherer
Copy link
Author

Works with #1556, thank you!

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

No branches or pull requests

2 participants