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

sys/rest_client: a multi-transport rest-client for RIOT #17785

Draft
wants to merge 55 commits into
base: master
Choose a base branch
from

Conversation

HendrikVE
Copy link
Contributor

@HendrikVE HendrikVE commented Mar 10, 2022

Contribution description

I wanted to share my progress on developing a REST-client for RIOT in the form of a draft PR to get some early feedback while still working on it. This does not mean that it is not working yet. On the contrary, I have a test setup which already does a lot of the stuff we could want. More on that below.

What does the REST-client has to offer?

  1. Modular transport protocols
    Supported right now are CoAP, CoAP with DTLS (CoAPs), MQTT and MQTT-SN
  2. Adjustable Quality of Service
    CoAP and CoAPs: confirmable and non-confirmable
    MQTT and MQTT-SN: QoS 0, 1, 2
  3. Supports IPv4 and IPv6 (should also support dual stack, but not yet)
  4. Built-in address resolution as convenience
  5. Support for all common (HTTP) REST methods (GET, PUT, POST, PATCH, DELETE)
  6. Get responses to your requests (even with pub-sub MQTT-based transport, whacky!)

How is this supposed to work? MQTT can't make calls to an HTTP API on the internet!

While this statement is true for native MQTT, it does not apply to CoAP: RFC 7252 specifies how CoAP can be cross-proxied to HTTP. For MQTT-based transport I developed a custom protocol which is build on top of MQTT and an application which will, similarly to a CoAP-HTTP-Cross-Proxy, translate the request to an HTTP call and send back a response.

I also did some modifications on CoAP components. In examples/rest_client you can find a demo application that will take a struct and serializes it into CBOR payload and sends it. So now you might think: "How is the HTTP API supposed to understand CBOR, they mostly talk JSON?" Well, I modified/extended californium and the CoAP code a bit. I introduced two new options. Target-Content-Format and Target-Accept. They work exactly like their counterparts Content-Format and Accept in terms of standardized media types.
RIOT => Californium: The modified californium cross-proxy will set Target-Accept as Accept header in the HTTP request and translates the payload from Content-Format to Target-Content-Format.
Californium <= HTTP API: When receiving the response the cross-proxy translates the payload from the format given in the HTTP Content-Type header to the format given by the Accept header of the original CoAP request, thus making it possible to send CBOR payload from the IoT device and receiving standard JSON on a common HTTP API. This might be a nice addition to the standard. CoAP interested people might have an opinion about that?

What software is needed for the backend?

The software stack I am using for the backend consists of the following components:
For MQTT: mosquitto + custom mqtt2http app
For MQTT-SN: paho.mqtt-sn.embedded-c + mosquitto + custom mqtt2http app
For CoAP(s): californium modified cross proxy example

And on top of all that use a HTTP API we would like to request.

What about security?

CoAPs is supported. The package wolfMQTT, which is used for MQTT-based transport, has TLS support built in, but my port to RIOT (#15969) does not yet support it and the code for MQTT-SN does not have any encryption built in yet at all. I asked the developers if anything is planned right now in this regard. They said it is on the list for future feature additions, but they did not mention a possible time for an implementation.
In one of my use cases for example the RIOT application talks to californium via DTLS and Californium is set up to talk to the Kafka REST proxy via mTLS.

The custom mqtt2http application does similar things.

Are there limitations?

We still need to keep in mind that we are operating on constrained devices. It is illusory to think that we could simply call every HTTP API out there. Some APIs just require too big request payloads or they produce too big responses. I tried it with the Kubernetes API. Responses mostly are way too big to even bother trying to work with it in most cases. Doing a request can be fine though.

What would be a use case?

In my case, I want to publish data and process it in an application which is following event-driven architecture. A popular software for this purpose is Apache Kafka. Kafka by itself is only accessible by different client libraries available for different programming languages. But Confluent also offers kafka-rest which will expose a Kafka instance via HTTP API.

An alternative approach to that specific use case could be to use e.g. Eclipse Hono, which can also be used with Kafka. But the power of the REST-client is that we, respecting the limitations, can call any HTTP API and therefore we are able to cover way more potential use cases.

What does it cost me to use it?

If you are interested in numbers you can ask me for it, I can provide (temporary valid) data and even nice graphs for build sizes and bytes sent over the network. But as an example: For my nucleo-f207zg the example gcoap needs about 59 kB ROM, while my test application needs 47 kB ROM for CoAP based transport. Mind that this comparison is not fair, because the test application does not need to parse user input. But it shows that it is possible to run the application on class 1 devices with ~100kB ROM.

Try it yourself

I created a project using docker-compose which will set up everything you need (You don't want to do it manually, really). You can find it here if you want to try it out. Simply run docker-compose up --build. Then run examples/rest_client e.g. with BOARD=nucleo-f207zg LWIP_IPV4=0 LWIP_IPV6=1 TRANSPORT_COAP_SECURE=1 QOS=0 make flash term.

P.S. Please ignore all commits except the topmost one. All other commits are only dependencies. I haven't cleaned up yet as many things are yet to change so this would be a waste of time ;)

jia200x and others added 30 commits January 18, 2022 11:05
The RSSI values reported by LoRa transceiver can be less than -127.
Therefore, `int8_t` is not enough. This commit defines the RSSI of
`netdev_lora_rx_info` as `int16_t` and adapt the drivers accordingly
(sx126x, sx127x).

(cherry picked from commit 9955a35)
…a/fix_rssi_val

[treewide] lora: use int16_t for RSSI value [backport 2022.01]
(cherry picked from commit 1025341)
…est_pkg_relic_stacksize

tests/pkg_relic: increase stacksize [backport 2022.01]
(cherry picked from commit 8b45ed8)
…tt_rtc_utils

drivers/rtt_rtc: select rtc_utils [backport 2022.01]
…dhoc_test_netif_multi

tests/pkg_edhoc: handle multiple interfaces [backport 2022.01]
…timer64_flag

sys/ztimer64/util.c: fix ztimer64_set_timeout_flag [backport 2022.01]
…f767zi_fix_adc_pin_config

boards/nucleo-f767zi: Fix adc pin config in periph_conf.h [backport 2022.01]
…/pr-release-notes-2022-01

release-notes.txt: add 2022.01 release notes [backport 2022.01]
Without initializing async_cb to NULL it might be a value != NULL,
which leads to sock->async_cb.gen() being called mistakenly in _netconn_cb.
@HendrikVE
Copy link
Contributor Author

This contains a bunch of unrelated commits. Note that you have to rebase your PR on top of current master rather than merging current master into your PR for Github to correctly detect whoch commits are part of your PR. Otherwise unrelated commits from master will pop up in your PR.

I believe all of these are just backport commits that don't exist in master. My branch is currently not based on master but on the 2022.01 release. I'll change that later though.

@HendrikVE
Copy link
Contributor Author

@chrysn I'm answering you here, because I think it is the better place to discuss.

I don’t have time for a good look at the PR right now, but in general “generic REST” is not well-specified, and often a topic of interoperability trouble when the implied abstraction is used differently by different parties.

Is there any model of generic-REST that you are using here?

If you are asking for an already available abstraction model then no. Using MQTT-based transport I had to abstract a few things that don't make sense for every implemented protocol. MQTT for instance requires that we connect to the broker first, so I introduced a rest_client_connect-function that will be a no-op for CoAP since CoAP simply sends requests without connecting first.
One key aspect that I changed for CoAP (besides the custom option for translating the body to another type) was the introduction of a custom option that will also deliver the original status code of HTTP in the response additionally to the truncated one provided by CoAP, such that the API is more HTTP-REST-like and the MQTT custom middleware does neither need to match CoAP status codes nor implement its own. Might be a good point to abstract further.
The term QoS is also problematic . While this is clear for MQTT, I believe it is not the right term to use for the reliability mechanisms in CoAP. Nevertheless I merged these within the API for now.
Otherwise the REST client is mostly based on CoAP and its feature set. What I'm doing with MQTT-based transport is to mimic those features experimentally, e.g. the Content-Type header has the same mapping of formats in MQTT as in CoAP.

@cgundogan
Copy link
Member

Getting all these mentioned transports under the hood of one REST client sounds intriguing. Rebasing to current master will definitely help with the reviews .. currently, the code is polluted with all these unrelated backport changes, which complicates the reviewing process (at least for me).

Out of curiosity: do you have additional resources (prosa, figures, etc.) that further conceptually describe how you plug all these transports together?

Another question (because the title says rest-client, maybe just a terminology clash?): does this also work for CoAP servers (so, on IoT devices)? From my first glance, I'd say yes?

@HendrikVE
Copy link
Contributor Author

HendrikVE commented Mar 22, 2022

currently, the code is polluted with all these unrelated backport changes

We could get rid of the backport commits, but mind that even a rebase to master does remove most of the other commits, because this PR has a few unmerged dependencies which it inherits all its commits from. Namely:

The important part here is 9f11b19
I just realized this commit is a bit polluted in itself, because it touches small parts of emcute, dns and gcoap but the changes are just a few lines and mostly from testing. I'll remove them once I rebased to master. Until then please ignore those lines :) My mid-term goal will be to have separate commits for each transport, but I didn't have the time to clean this up yet.

Out of curiosity: do you have additional resources (prosa, figures, etc.) that further conceptually describe how you plug all these transports together?

Not for the client itself, but for the overall architecture (with two different HTTP APIs as examples):
overview_architecture

because the title says rest-client, maybe just a terminology clash?

As you can see above my goal is to communicate with common HTTP REST APIs that are not IoT-aware. Calling it HTTP client is even worse in my opinion, because it does not speak HTTP (but it could!). Because of this and the fact that HTTP and REST are somewhat synonymous for this kind of API in the web (see https://github.com/confluentinc/kafka-rest) I decided to go for the name rest_client for now. I'll happily take suggestions for a better name though :)

does this also work for CoAP servers

Do you mean by using CoAP Observe? This is not planned right now. I believe that would require yet another component that would subscribe on the node and manage all data changes by doing HTTP calls. But this could be done on a higher hardware level like a Raspberry Pi. The focus here is on keeping the logic for doing requests on the microcontroller itself, working in client mode, and evaluate the feasibility thereof. The target is Class 1 microcontrollers and preliminary measurements are promising. Well... it really depends on the API we want to request, but RAM and ROM requirements are fine.

@Citrullin
Copy link
Contributor

This is really interesting. Never really thought about it, but it enables some interesting use-cases. Especially when it doesn't have such a big impact of performance or binary size. Would be interesting to compare an app 1 to 1.

@HendrikVE HendrikVE force-pushed the draft/rest_client branch from 9f11b19 to 2b14bbf Compare May 12, 2022 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: boards Area: Board ports Area: build system Area: Build system Area: CoAP Area: Constrained Application Protocol implementations Area: doc Area: Documentation Area: drivers Area: Device drivers Area: examples Area: Example Applications Area: Kconfig Area: Kconfig integration Area: LoRa Area: LoRa radio support Area: network Area: Networking Area: pkg Area: External package ports Area: sys Area: System Area: tests Area: tests and testing framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.