UDP received DGRAM boundaries lost: read(), parsePacket(), available() all broken

@peekay123 - It is entirely valid to have a few outstanding datagrams. One need not always sender-write, reader-read in strict turns. It is also valid, in the UDP protocol, for datagrams to go missing, and for datagrams to be discarded if not read. That my example at top of post is artificial in that there is a deliberate 1 sec wait between reading datagrams could mimic a real world situation where the reader cannot always immediately attend to the writer’s datagrams. Or where there is substantial work to be done as a consequence of each read datagram. Or perhaps the reader is just bogged down. What breaks the UDP protocol is the concatenation of the datagrams so that one read returns not exactly one or zero datagrams. It matters not one iota what the application code is, the underlying UDP protocol says that cannot happen. Spark UDP is broken in this respect.

While that's true, that doesn't deal with @psb777's issue, which is that message boundaries are not preserved. With UDP, messages can be lost, duplicated, or out-of-order. However, if a UDP datagram is received, it's received in its entirety, and only one datagram is read per read (this last point is what @psb777 is complaining about). Basically, the spark is violating message boundaries when multiple datagrams are received per read. Now, this might not affect many applications, but that's not how UDP is supposed to work.

References:

User Datagram Protocol - Wikipedia (quote from the UDP section: "Datagrams – Packets are sent individually and are checked for integrity only if they arrive. Packets have definite boundaries which are honored upon receipt, meaning a read operation at the receiver socket will yield an entire message as it was originally sent.")

Transport layer - Wikipedia (under UDP, "Preserve message boundary" is "yes")

https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/7-Beta/html/Networking_Guide/ch-Configure_SCTP.html#s1-Introduction_to_SCTP (see table 9.1)

Converging NGN Wireline and Mobile 3G Networks with IMS: Converging NGN and ... - Rebecca Copeland - Google Books (middle of page 428: under "UDP", "Preserve message boundary" is "yes")

2 Likes

Unfortunately, with the spark, it's best to assume a pared-down, mostly-compliant networking feature set. However, with parsePacket(), I think the intention is for you to read only the number of bytes returned by parsePacket(), to distinguish the packet boundaries. Your example is reading a fixed 81 bytes. However, I haven't tried this, and so I'm just guessing.

@Raldus - No, I am not reading 81 bytes, but maybe I misunderstand - the documentation on the UDP.read() call is very lacking. I thought the numeric second argument to UDP.read() was an upper limit, the size available at the address supplied in arg 1, not the number of characters requested. I thought what 81 meant was give me the entire packet or 81 characters, whichever is the smaller. But how could I ever know the number of characters I should read? Is that the purpose of the UDP.available() call? The UDP.parsePacket() call is used by me to null-terminate the received data at what I think should be the packet end, but the 3 (or 4 if the nul-terminator is counted) which I am expecting that call to return (in my artificial example) is not what I get. UDP.available did not seem to help. I did experiment with it, but I will try it again. I tried again, UDP.available() returns the same as UDP.parsePacket().

An alternative way of reporting what I hope all might now agree is a bug in UDP might be this: UDP.parsePacket() is broken - it returns not the size of the next datagram to be UDP.read() but rather the total size of all the datagrams waiting to be read.

OK, I am clearly not contributing in a positive way here but I still believe you are mistaking the common BSD implementation and the spec (RFC 768 and RFC 1122). Let’s just move on. I have edited the title to reflect the limitation.

I don’t think there is going to be “fix” for this anytime soon since I found this page which says in part (emphasis mine):

Known Limitations

  1. CC3000 supports a maximal MTU size of 1468 bytes
  2. CC3000 supports maximum delayed Ack of two packets
  3. When running on a very slow TCP connection, it might be required to change the Tx flow control to 2 packets instead of 4. This is in order to avoid the delayed Ack problem mentioned above
  4. Smart Config - Multiple devices can’t be configured to different APs simultaneously
  5. Smart Config - Supports 11bgn, 20Mhz, AP’s using channels 1-13
  6. Smart Config - Key length is limited to 31 characters when working with AES encryption
  7. IP fragmentation is not supported in the CC3000 network stack. When packets are fragmented on the IP level, CC3000 cannot receive the packets and defragment them. The limitation applies to TCP and UDP packets
    8. When the recvfrom() API is invoked by the host, UDP data is sent from CC3000 to the host according to the requested size, not one packet at a time.
  8. Connection process will not start for APs with RSSI lower than -75dBm
  9. “wlan_connect” API supports connection to SSID and not to BSSID
  10. In CC3000 module, MISO is not set to Hi-Z when nCS is deasserted.

@peekay123 I’m in the knows too little but it works anyway category. More of the same follows!
OK @psb777 I stand corrected. Chunking does require the TCP protocol. Strangely my test data source was Microsoft’s version of UDP so quite how Python got past the header is a mystery or maybe PiPy takes the same shortcuts that Spark does.

I think the Spark UDP.available() function is based on the Arduino Ethernet Library which describes it as:
“Get the number of bytes (characters) available for reading from the buffer. This is data that’s already arrived.
This function can only be successfully called after UDP.parsePacket().
available() inherits from the Stream utility class.”

This isn’t what the RFC 768 protocol says but it makes for compact code.

Taking a look at the Spark code in spark_wiring_UDP.cpp and the associated header I think confirms this.Maybe you can get at the length bytes with _remoteSockAddr.sa_data[6] and _remoteSockAddr.sa_data[7].

If you do unpick the correct message length what will you do to ensure that the message is intact and the length is read correctly? I’m pretty sure that Spark doesn’t process the checksum either.

@bko - I am not confusing the relevant RFCs and the common implementation thereof. Nothing in what you quoted from some RFC contradicts any common BSD sockets implementation I have seen anywhere. But leave you and me out of it: If the RFCs and the almost universal implementation of BSD sockets were in conflict I would be shocked NOT to find reference to this popping up almost everywhere you cared to look. The RFCs are usually slavishly implemented even at least once to the extent that a mis-spelling in an RFC being reflected in code and data https://en.wikipedia.org/wiki/HTTP_referer#Origin_of_the_term_referer. Where the RFCs do not reflect common implementations of whatever this is usually very well known.

How the main CPU and the CC3000 divide tasks I do not (yet) know. The UDP datagram header contains the size of the datagram. Is the UDP packet header consumed by the CC3000? And not available to the main CPU? Well, then TI needs to be informed of the bug. Otherwise it can be fixed in the main CPU.

There are other aspects to this bug - not just the size reported by UDP.parsePacket(). E.g. A common UDP server application might be to receive packets from anywhere and to acknowledge receipt. if two datagrams are received on a UDP socket, and these are from two different machines, and they are lumped together by UDP.parsePacket() then what does UDP.remoteIP() and UDP.remotePort() report? If I am clever enough to know the length of the first datagram (and remember there is no way to know and you don’t have control over what remote machines send you) and I UDP.read() that many bytes and I call UDP.parsePacket() again, is a different IP-address and port-number now returned by UDP.remoteIP() and UDP.remotePort() ? No. I cannot acknowledge receipt. I don’t even know from what machine the datagram was sent.

So, I’m going to change this topic title to something that represents the extent of the issues with UDP on the Spark. What else can be done? It is not just a packet size problem.

@phec - I’m unsure what you mean by “chunking” but what I wrote at the top, in the initial posting, is correct. UDP datagrams arrive as sent (if they arrive at all) and not necessarily in the correct order. TCP data arrives in a stream, byte (n+1) always arrives immediately after byte (n). You can read the stream in any size (chunk?) you like. And even if you attempt to read in exactly the same size as sent, even if this is constant size, there is nothing in the TCP protocol which guarantees that your attempt will succeed. The sender may send 100, 100, 100. And you may attempt to read in 100 byte sizes (chunks?) to find that the reads return 100, 20, 100, 80. bytes. Ordinarily, where not too many network hops are made, and where traffic is not very congested, you will see 100, 100, 100 but the point is this, if you want to write robust code, you must not rely on that. Plenty of programmers do idiotic things such as write one database record per TCP sendto() call and then are surprised that the error-corrected reliable TCP connection does not always result in one recvfrom() per sendto(). The network programmer always has to choose between two things: Either he must worry about message boundaries and use TCP or he must worry about lost/duplicated/out-of-sequence messages and use UDP. Sometimes the latter is much easier. But not (currently) on the Spark Core!

I start to suspect that Spark UDP is derived from Spark TCP. You quote UDP.available() as being inherited from the Stream class. UDP is not a stream. Not in the way Stevens (and doubtless the RFCs) use the term. TCP uses SOCK_STREAM sockets - UDP uses SOCK_DGRAM.

Where do I find documented “remoteSockAddr.sadata[6] and remoteSockAddr.sadata[7]”?

According to the documentation which, as you say, has been lacking, parsePacket() returns “the size of a received UDP packet”. I would expect that to refer to the size of whatever packet it just received. However, you seem to have shown that this isn’t the case.

And, even if it did work correctly, read() is still a problem. I misunderstood the second parameter, and so it appears that it really isn’t possible to currently get true UDP semantics from the spark.

1 Like

Since I have no insight into UDP specs anywhere near to all of you here, maybe I can throw in my unbiased opinion :wink:
@psb777, have you tried creating distinguishable UDP packets and then examined the whole bunch of packets you received?
You could also try to do a udp.read( sbuf, Math.min(packetSize, 81)); and if you do a subsequent udp.parsePacket and udp.read with the new packetSize (repeating as long packets are available), you might actually get one valid packet per read with a set of consistent IP and port.
Since the order of packets is not certain the CC3000 might give you the packets FIFO or LIFO but this wouldn’t matter. However this could be found out by looking at the distinguishable packets.

And it might turn out that all of you are right in their own way and just didn’t see the problem/view of the other (tunnel vision :wink: ).

Since you are dealing with the CC3000 and the STM32F you might see some kind of apparent fault in the UDP implementation, but it could actually turn out that the CC3000 does everything correctly (one or none packet) but it has also implemented some buffering of packets for the STM32F to request them one by one and the UDP class is not actually a UDP stack implementation but merely a way of making use of the data received by the CC3000.

( No idea if I’m making any sense here :blush: )

@ScruffR - I have conclusively shown that both UDP.packetSize() and UDP.available() returns not the size of one packet but the total size of the packets queued for reading. And there are 1,000,001 things I have not tried but it seems nuts to adjust the clutch cable to see if that makes the windscreen wipers work :smile: and you would be annoyed if a mechanic charged you for his time so to check. UDP demands I see (even duplicated) packets one by one, not concatenated. HOWEVER, I happen to know from my real world example - multiple sources of UDP datagrams being sent to my Spark UDP server app - that the distinguishable packets from two different sources get mangled together by UDP.packetSize(). The problem is this: The packets are human-distinguishable [in hindsight by looking at a log of a packetsniffer] but not Spark-UDP-distinguishable.

But you like others helpfully try to provide workarounds for my sample not-for-real app initially posted rather than recognise that is not the issue - Spark UDP is broken.

I know it would be nice were everyone correct but that is not or should not be our motivation. My motivation is that a serious bug in Spark UDP be recognised.

Sorry, I must have missed that part where you have stated that the packetSize didn’t get returned as 3 for your test packets - my fault :blush:

Maybe this thread should be brought to the attention of @satishgn and @Dave, too.

pbs777, after some researching on the Arduino ethernet implementation of UDP, I have to agree with you that the Spark versions of parsepacket() and available() are not functioning as they should. This is also the cause of my earlier confusion as I expected parsepacket() to return the PACKET size not the total received data size.

The Arduino version of parsepacket() uses a "tcp method" to extract the IP, source port and UDP data field length from the received packet. So parsepackate() returns the length of the data field of a "new" packet as any old packet data is flushed first.

The Arduino version of available() returns the UDP packet data length field right after the parsepacket() call and reduces by 1 as bytes are read from the packet.

The Spark version is therefore NOT consistent with the Arduino implementation or even its own documentation:

parsePacket()... Checks for the presence of a UDP packet, and reports the size.
available()... Get the number of bytes (characters) available for reading from the buffer

As such, as pbs777 pointed out, it needs to be fixed.

2 Likes

Hey Guys,

I forwarded this thread to @satishgn and @zachary to hopefully dig in and comment. Also if we haven’t yet, is there a github issue to track this, I think that would help focus efforts. https://github.com/spark/core-firmware/issues :slight_smile:

Thanks guys!
David

1 Like

@BKO found the underlying issue. Either TI have to fix this "Known Limitation" or, if this is at all possible, Spark need to do it. But the picture I get slowly emerging from my own personal Spark Core ignorance cloud makes me suspect the CC3000 is not passing the UDP datagram header on. Can the CC3000 be queried for it?

Sorry I was wrong about my earlier comment, so deleted that post.