14. Datagram service

org.arl.unet.Services.DATAGRAM

Unets are all about sending datagrams between nodes!

The DATAGRAM service is, therefore, one of the fundamental services that many agents provide. However, different agents have very different capabilities in terms of what they can do with datagrams. Let’s take a look at what the service offers, and how to use it effectively.

14.1. Overview

14.1.1. Messages

Notation guide

Every request message requires a response. When describing request messages, we also specify what responses to expect, through the notation:

  • request message possible response messages short description

On the other hand, notification messages do not have corresponding responses, so we only describe them:

  • notification message short description

Agents offering the DATAGRAM service support messages to transmit and receive datagrams:

14.1.2. Parameters

Agents offering the DATAGRAM service support the following parameter:

  • MTU — maximum datagram size in bytes

  • RTU — recommended datagram size in bytes

14.1.3. Capabilities

Agents may support several optional capabilities:

Agents capable of fragmentation may break a datagram into smaller datagrams, transmit each of them across the network, and reassemble them on the peer node.

If an agent advertises relaibility, it is able to acknowledge the successful delivery of the datagram. Reliability is enabled on a per-datagram basis by setting the reliability flag in the DatagramReq . Depending on whether the datagram could be successfully delivered or not, one of the following notifications is generated:

Agents capable of reporting progress do so by periodically sending the following notification for long data transfers:

  • DatagramProgressNtf — sent to requestor on the transmitting node, and agent’s topic on the receiving node

If an agent supports cancellation, it honors the following request for stopping an ongoing data transfer:

Agents advertising this capability prioritize datagrams with higher priority indicated in the DatagramReq .

Agents that spool DatagramReq over extended periods of time usually advertise this capability. They discard DatagramReq that are undelivered after the time-to-live ( ttl attribute of the DatagramReq ) has expired.

Agents advertising this capability may apply data compression to incoming DatagramReq messages. If it does so, the peer agent will decompress the data before sending the DatagramNtf .

14.2. Examples

Fire up your 2-node network again, and open two browser windows — one connecting to node A’s shell, and the other to node B’s shell.

On node B:

> agentsForService(org.arl.unet.Services.DATAGRAM)
[transport, router, uwlink, phy]
> phy.MTU
56
> uwlink.MTU
848
> uwlink << new CapabilityReq()
CapabilityListRsp:INFORM[RELIABILITY,FRAGMENTATION]
> subscribe uwlink

We obtained a list of agents which provide the DATAGRAM service. We checked the MTU of the phy and uwlink agents, and found them to be 56 bytes and 848 bytes respectively. We decided to use the uwlink agent to communicate over a single-hop link between node A and B, and checked its capabilities. We found that it supports reliability and fragmentation. We then decided to listen to all datagrams received by node B’s uwlink agent by subscribing to its topic.

On node A:

> uwlink << new DatagramReq(to: 31, data: new byte[64])                               (1)
AGREE
> uwlink << new DatagramReq(to: 31, data: new byte[64], reliability: true)            (2)
AGREE
uwlink >> DatagramDeliveryNtf:INFORM[id:4dda055e-a533-401f-89c8-a01065ca5d70 to:31]   (3)
> uwlink << new DatagramReq(to: 37, data: new byte[64], reliability: true)            (4)
AGREE
uwlink >> DatagramFailureNtf:INFORM[id:bc65e643-6161-4b06-913c-3ff4f6985d36 to:37]    (5)
> uwlink << new DatagramReq(to: 0, data: new byte[64])                                (6)
AGREE
> uwlink << new DatagramReq(to: 0, data: new byte[64], reliability: true)             (7)
REFUSE: Reliability not supported for broadcast
> uwlink << new DatagramReq(to: 31, data: new byte[1024])                             (8)
REFUSE: Data length exceeds MTU
1 Send an unreliable datagram to node 31.
2 Send a reliable datagram to node 31.
3 Successful delivery of reliable datagram reported.
4 Send a reliable datagram to node 37. Since node 37 does not exist in this network, this should eventually fail.
5 Delivery failure reported (after trying for a few minutes).
6 Broadcast an unreliable datagram.
7 Broadcast request for reliable datagram is refused, as reliability requires a response from peer node and therefore cannot be supported on broadcast.
8 Datagram transmission request for data larger than MTU is also refused.

If we look at the shell for node B, we should see the 3 successfully delivered datagrams:

uwlink >> DatagramNtf:INFORM[from:232 to:31 (64 bytes)]
uwlink >> DatagramNtf:INFORM[from:232 to:31 (64 bytes)]
uwlink >> DatagramNtf:INFORM[from:232 (64 bytes)]
Agent uwlink uses the PHYSICAL service (agent phy ) to deliver the data. Since the phy.MTU is only 56 bytes, but our datagrams were 64 bytes, unbeknownst to us, the uwlink agent must have been fragmenting these datagrams and reassembling them on the other side!

14.3. Short-circuit delivery

We were able to successfully deliver datagrams from node A to node B in the examples in the previous section. We not only saw the DatagramNtf messages on node B, but also got DatagramDeliveryNtf on node A if reliability was enabled.

Let’s try it again, but with a small difference. On node A:

> uwlink << new DatagramReq(to: 31, data: new byte[32])
AGREE

We transmitted a smaller datagram, and node A happily accepted it for delivery. However, if we look at the shell for node B, we don’t see a DatagramNtf message corresponding to the datagram, even though you had already subscribed to uwlink ! What’s going on? Let’s try it again, but this time enable reliability:

> uwlink << new DatagramReq(to: 31, data: new byte[32], reliability: true)
AGREE
uwlink >> DatagramDeliveryNtf:INFORM[id:4aaa86e5-9a56-46f8-bc1a-f6be33af03a4 to:31]

We see that the datagram was indeed delivered! And now, if we look at node B’s shell, we’ll see the delivery notification:

uwlink >> DatagramNtf:INFORM[from:232 to:31 (32 bytes)]

It seems that enabling reliability successfully delivered the datagram, but otherwise the DatagramNtf message did not appear on node B’s shell! You can try this many times, and the result will be the same. So it can’t be random packet loss in the network either. What’s going on?

To try and troubleshoot this, let’s subscribe to notifications from the phy agent to see if the data is arriving at the physical layer. On node B:

> subscribe phy

On node A, transmit the unreliable small datagram again:

> uwlink << new DatagramReq(to: 31, data: new byte[32])
AGREE

On node B, we now see a couple of notifications:

phy >> RxFrameStartNtf:INFORM[type:DATA rxTime:3956973678]
phy >> RxFrameNtf:INFORM[type:DATA from:232 to:31 rxTime:3956973678 (32 bytes)]

The first notification says that the physical layer detected the start of a data frame. The second notification is for a received frame with 32 bytes from node 232 to node 31. That’s our datagram!!! But why is it delivered by phy and not uwlink , when it was sent by uwlink on node A? And why is it a RxFrameNtf instead of a DatagramNtf ?

Let’s solve the second mystery first. An RxFrameNtf is a subclass of DatagramNtf , so it is indeed a DatagramNtf message. We can easily verify this on node B:

> ntf
RxFrameNtf:INFORM[type:DATA from:232 to:31 rxTime:3956973678 (32 bytes)]
> ntf instanceof DatagramNtf
true

Variable ntf contains the last notification received. It is the RxFrameNtf , and it is indeed an instance of DatagramNtf . So, we indeed got the datagram on node B, and it was delivered as a DatagramNtf with the correct metadata.

But why was it sent on phy agent’s topic and not uwlink agent’s topic, like all other datagrams we transmitted?

This is due to an optimization known as short-circuit delivery (introduced in UnetStack 3), depicted in Figure 8 . The uwlink agent on node A looked at the unreliable DatagramReq for 32 bytes and realized that it is within the phy agent’s capability (no reliability needed, and the datagram size is less than phy.MTU ) to deliver this without the help of the uwlink agent. It delegated the task to the phy agent, which in turn send the datagram to its peer on node B, and therefore it was delivered to us by the phy agent on node B. This delegation not only reduces computation, but more importantly reduces the overhead of link headers in the frame, and therefore save valuable bandwidth in a resource-constrained underwater network.

Short-circuit delivery is not only done by uwlink , but by all agents supporting the DATAGRAM service. If a downstream agent is capable of delivering the datagram, the delivery is delegated automatically.

As a result of short-circuit delivery optimization, you need to subscribe to all DATAGRAM service providers to receive DatagramNtf messages, and not just the one you send the datagram via.
shortcircuit
Figure 8. With short-circuit delivery, uwlink on node A recognizes the DatagramReq to be within the phy agent’s capability, and delegates it without adding any headers. On node B, the received frame is directly delivered as a DatagramNtf by the phy agent, since uwlink functionality is not required.

On node B, we should have done this in the first place:

> agentsForService(org.arl.unet.Services.DATAGRAM).each { subscribe it }

This single-liner in Groovy iterates over the list of agents providing the DATAGRAM service, and subscribes to the topic of each agent in that list.

Agents should use the call subscribeForService(org.arl.unet.Services.DATAGRAM) instead. This call subscribes to all agents providing teh DATAGRAM service, but has an added advantage: it also asks the agent to keep track of new agents that are added to the stack later, and subscribes to them if they provide the DATAGRAM service.

14.4. Datagrams and the UnetSocket API

The UnetSocket API also supports delivery of datagrams. Let’s try it. On node A:

> s = new UnetSocket(this);
> s.send new DatagramReq(to: 31, data: new byte[32])
true

On node B, we will see the datagram delivery:

uwlink >> DatagramNtf:INFORM[from:232 to:31 (32 bytes)]

Note that we did not have to specify an agent or service when making the datagram request via the UnetSocket API. An appropriate agent was automatically selected by the API for us. In this case, the uwlink agent was used by the API to deliver the datagram.

<<< [Services and capabilities] [Physical service] >>>