Open faithanalog opened 3 years ago
Personal opinion part: I think that more generally the attempt to implement udp traffic under the api designed for tcp is maybe not a great long term solution.
The current implementation implements udp with patches on functions originally designed for tcp connections. So it runs startClientTcp()
which in the case of udp
just calls beginPacket()
and returns. Then it runs startServerTcp()
to start a udp server on the same port locally to receive responses.
This doesn't really align with how UDP actually works as a connectionless protocol.
Additionally, because a udp socket calls startServerTcp
with the port of the target destination, the current API does not allow you to initiate UDP traffic to multiple destination hosts on the same port simultaneously. You must call close()
on one before connect()
for the next, because the local port bound for responses will be taken otherwise.
In the long term it may be good to implement an API specifically for UDP that aligns more with how UDP works to allow users to better take advantage of the strengths UDP. I would be happy to help implement that as well.
You have hit the nail on the head. Correct on all points.
My goal with the original UDP PR #97 was to simply get it working enough for my use case, knowing very little of what the "general" UDP use case would be. Also there were pre-existing stubs in the code labeled as "not implemented", I assumed this was the desired api and just filled it in. I'm not the networking person whatsoever.
To get this working in general, it may require changes to both nina-fw as well as this library, but maybe not. WiFiNINA is another client of nina-fw that would need to be kept in sync. Those dependency chains are the reason I didn't want to mess with nina-fw.
My recommendation would be as you suggest, to break the interface between tcp and udp. Thus making a separate and more explicit beginPacket / write / endPacket interface.
WiFiUdpSendReceiveString would me my canonical example starting place, using the interface in WiFiUdp
If you do take this up, I'd be more than happy to lend a set of eyes for code review, though I technically have no power here.
Yeah I don't think I wrote it down how I had it in my head but the initial patch made a lot of sense as a low-impact way of initially getting some functionality in place.
To get this working in general, it may require changes to both nina-fw as well as this library, but maybe not. WiFiNINA is another client of nina-fw that would need to be kept in sync. Those dependency chains are the reason I didn't want to mess with nina-fw.
Oh hey I was actually just about to write something to this extent. yeah this is the big challenge is the question of where to implement the change. I think the cleanest / most efficient solution would involve changes to nina-fw.
But putting those things aside for practicality, I do think a proper UDP API can absolutely be implemented purely in esp32spi. I think all of the raw components of the underlying API that you'd need are already exposed:
_START_CLIENT_TCP_CMD
provides beginPacket()
_SEND_UDP_DATA_CMD
provides a way to write the packet_SEND_UDP_DATA_CMD
provides endPacket()
_START_SERVER_TCP_CMD
provides begin()
for receiving data_GET_DATABUF_TCP_CMD
provides read()
for reading received dataAnd all of these are already used on the existing UDP implementation, so we know they work. So it may be possible to implement most or all of an ideal UDP-specific API in terms of these without changing nina-fw, in a way that would also allow future changes to nina-fw to make it better. Basing it on the current WiFiNINA UDP API makes a lot of sense to me, thanks for pointing that out.
Something I want to note so I don't forget is _START_SERVER_TCP_CMD
allocates a socket on the esp32 but only _STOP_CLIENT_TCP_CMD
de-allocates it, which is just a thing to look out for so we don't accidentally design an implementation for receiving data that drains the esp32 of available socket IDs.
I'm happy to write a draft implementation in a PR when I get some time for it.
when I get some time for it.
That's what I keep telling myself..
Ran into this exact problem, unfortunately only found this issue after following all the threads myself.. Given nobody seems to have had the time to fix it in the meantime (totally understandable) maybe it'd be good to at least document this pitfall for now
@f0x52 you interested in submitting a PR with a warning added to the readme file or documentation?
This issue also seems to affect UDP server. This UDP server code works: https://gist.github.com/anecdata/61dfb2e5f4649a1a42885f8e9314800a But if the server attempts to write a reply back to the client, the buffer gets appended rather than overwritten.
I've tried a number of variations, and it doesn't seem to work to close and re-open the socket before sending, or to create a new socket to send the reply.
Tested:
start_server
returns the same socket number and still sends the old data too.
Given it's 3 years on now and my life circumstances have changed, I ought to say I'm not likely to find time to write a fix for this. I may be able to help review someone else's PR if they write one.
Later today, I will try the beta firmware and see if it works or does something different.
Verified this still happens with the 1.8.0 beta
. Might be best to get that tested/merged first and then figure out how to fix this.
cc @dhalbert
My Platform:
The problem:
udp.send() appends to the write buffer instead of replacing it. This results in the following behavior:
will actually send
and this continues until the esp32's internal write buffer is full. at which point calls to send() will only send the buffer and not append anything new.
The expected behavior is that a call to
write()
will send the provided data as a datagram packet and nothing else.Why this happens
In the nina-fw WiFiUdp.c implementation we can see the following:
_sndSize
tracks the write buffer size for the current packetbeginPacket()
sets_sndSize
to 0. This is the only place where_sndSize
is set to 0.endPacket()
does not set_sndSize
to 0.The only nina-fw command that calls
beginPacket()
is the startTcpClient() command which, despite the outdated name, is also used to initialize UDP sockets.sendUDPData() only calls
endPacket()
. It does not callbeginPacket()
.socket_write()
has code roughly equivalent (with simplification and no handling of large buffers) to this:_INSERT_DATABUF_TCP_CMD
writes data to the UDP socket buffer, and then_SEND_UDP_DATA_CMD
callsendPacket()
which sends the current buffer as a datagram packet.beginPacket()
is never called during asocket_write()
.Workaround
Currently you can work around this problem by closing and re-opening the socket before every write. So every
write()
now becomesThe
close()
is necessary becauseconnect()
also opens a "server" port for receiving responses, and soconnect()
will fail if that server port is already open.Potential fixes
It's not entirely clear to me whether this is a bug in esp32spi or in nina-fw. Changing esp32spi's
socket_write
to send a_START_SERVER_TCP_CMD
message after sending_SEND_UDP_DATA_CMD
would fix this issue. Changing nina-fw'ssendUDPData()
command to callbeginPacket()
again afterendPacket()
would also solve this issue. So would changing both to add a new command specifically to callbeginPacket()
into nina-fw and using it in esp32wifi.So I'm happy to write a patch to fix this, but I don't know which project i should be writing the patch for.
Example code to cause this problem