Spark -> Cloud Bandwidth Requirements

Hi,

I have been playing with the Spark Core for a while and loving it. I am looking at using it to monitor several sensors from a remote location with a strict bandwidth limitation on Wifi.

Is anyone able to provide any information on how much bandwidth (number of bytes) a single update of a single spark.Variable of type ‘double’ might consume? Even though the variable is - I assume - four bytes, I’d also assume there is a fair amount of “fat” around the variable to form the POST request (assuming variables are updated via REST) and probably a little more for security negotiation.

If no one can provide any input then perhaps I’ll have to breakout Wireshark but, being a wireless device, it’ll be tricky to get it set up.

Thanks in advance,
Ian

Not sure if this helps: https://github.com/spark/firmware/blob/feature/hal/communication/src/spark_protocol.cpp#L374

https://tools.ietf.org/html/draft-ietf-core-coap-03#section-3.1

Hi Kenneth,

I hadn’t realised Spark used CoAP under the covers so that’s really helpful, cheers!

From the firmware source, it looks like 4 byte payload for all types other than string giving a 16 byte packet. Curiously, it would seem that the URI of the CoAP PUT request (I imaging something like ‘coap://cloud.spark.io/[deviceId]’) will end up being the biggest part of the message.

So assuming (big assumptions ahead) a 16byte packet, a 22 byte address and a 24 byte device id, we should be looking at something like 62 bytes per update (probably padded to 64 bytes but the comms infrastructure).

Think I’ll see if I can capture some traffic to check some these assumptions!

Anyway, thanks for the help Kenneth. At least got me moving in the right direction.

Cheers,
Ian

1 Like

Actually, just looked through this code / protocol a little more and realised that the communications channel is in fact stateful and all CoAP packets will get sent to a singular endpoint (probably translated from DNS to an IPv4/v6 address just the one) meaning that there is far less overhead on each packet than I had assumed above.

So, this gives us (optimistically):

  1. 16 bytes for the variable update packet and response at the application level
  2. 8 bytes of UDP header at the transport layer
  3. 20 bytes IPv4 header at the internet layer
  4. Plus some framing at the link layer

Each PUT request will be accompanied by an ACK which will effectively double the above so lets say a single update of a single double variable is likely to require at least 88 bytes of data with 128 byes being a ‘reasonable’ estimate (given my general ignorance here).

Be very interested to hear if anyone can shine more light on this.

1 Like

@zachary is the best man for this kind of explanation :smiley:

Thanks for the ping @kennethlimcp — and good question @ibebbs.

TL;DR:

  • TCP packet from cloud to core depends on length of variable name
    • 1–7 characters: TCP payload is 18 bytes
    • 8–12 characters: TCP payload is 34 bytes
  • TCP ACK
  • 18-byte TCP packet from core to cloud with variable value
  • TCP ACK

Add all the various framing overhead, and if packets get dropped then things get repeated at the TCP level.

All the Detail

There’s an ASCII chart in the CoAP spec that I often go to as a reminder of the format. The first four bytes (version, type, token length, code, message ID) are always present; the minimum message size in CoAP is 4 bytes.

Every message is encrypted with AES-128-CBC, which means that the ciphertext length will always be a multiple of 128 bits, which is 16 bytes.

Additionally, the first 2 bytes of every message is the length of the rest of the encrypted message.

  • A plaintext of 1–15 bytes will get encrypted to a single 16-byte block and prefixed with hex 0010 to say that the length of the ciphertext is 16. That’s 18 total bytes.
  • A plaintext of 16–31 bytes will get encrypted to two 16-byte blocks, a total of 32 bytes, and prefixed with 0020 to say the length of the ciphertext is 32. That’s 34 total bytes.

This ultimately means that most messages on the wire in the Spark protocol are 18 bytes. They get longer when long strings are involved, and they’re always a multiple of 16, plus 2: 18, 34, 50, 66…

These are the TCP payload lengths, so then there’s framing overhead and a TCP ACK each time in the happy case or some repeated messages if things get dropped.

Variable Request

You can see in received_message that a variable request from the cloud to the core is a confirmable GET request with the first path option set to v.

There is a single-byte token that is used to correlate the request with the response.

The name of a variable may be 1–12 characters long.

Here’s an example of the shortest possible variable request CoAP message in xxd format:

0000000: 4101 8877 66b1 7601 76                   A..wf.v.v

Here, 4101 means this is a confirmable GET request with a single-byte token. The message ID is hex 8877 and the token is hex 66. The next b176 makes this a variable request because the first Uri-Path option is v. The name of the variable is as short as possible, a single character: v; that’s the final Uri-Path option 0176.

Now here’s an example of the longest possible variable request message, where the name of the variable is 12 characters long.

0000000: 4101 beef cab1 760c 6177 6573 6f6d 6573  A.....v.awesomes
0000010: 6175 6365                                auce

Here 4101 is the same, indicating a confirmable GET request with a single-byte token. The message ID is beef, and the token is ca. The next two bytes are still the same Uri-Path option of v: b176.

The remaining 13 bytes are the second Uri-Path option, representing the variable name. The first byte 0c means the option value length is 12 bytes. The final 12 bytes are the variable name: awesomesauce.

If the name of the variable is 7 characters long, then the whole CoAP message is 15 bytes long, the maximum plaintext that fits in a single AES-128-CBC block. If the variable name is 8 characters or longer, we need a second encrypted block.

Variable Value Response

The current value of the variable is returned to the server in what the CoAP specification refers to as a “piggy-backed response”. There’s no separate CoAP ACK; the ACK contains the response.

Side note: We do this with variables because retrieving them is fast. However, when calling Spark.functions we send an empty CoAP ACK, followed by a separate response since we don’t know how long the user code may take to execute.

The variable_value function with the final parameter type double shows exactly what the response looks like.

  • 4-byte header
  • 1-byte token
  • 1-byte payload marker
  • 8-byte payload of double-precision floating point value

That totals 14 bytes which gets AES encrypted into a single 16-byte block, plus the 2-byte length prefix, giving an actual encrypted TCP payload of 18 bytes.

1 Like

Also, for some ready-made examples of what you might see on the wire, you can check out the tests, where arrays named things like expected and ciphertext are either what the core expects to receive from the cloud, or what the cloud could expect to receive from the core. These examples sometimes contain the 2-byte prefix and sometimes not, depending on context.

Here’s a test that covers exactly the exchange you’re asking about, named event loop responds to variable request with variable value. It has both an example ciphertext that comes from the cloud and the ciphertext with which the core responds.

Great stuff @zachary! Exactly what I needed. Thanks for taking the time to explain the message exchange protocol, really interesting.

I didn’t realise the variable update was actually a two phase process (get the token, send the update). Confused why the CoAP protocol would use two messages for this when they’re trying to squeeze down message exchange to a minimum (as per the “piggy-backed” ACK message).

Anyway, I should be able to work out a rough approximation of how many updates I can make per hour from this, cheers. One immediate take away: use variable names that are less than 8 characters!