mirror of
https://github.com/espressif/openthread.git
synced 2026-07-29 15:17:47 +00:00
[coap] add support for RFC7641 (#4396)
* [coap] Add option scanning functions
In a few places in the OpenThread code-base, the following construct is
seen:
```
otCoapOptionIterator iterator;
otCoapOptionIteratorInit(&iterator, message);
for (const otCoapOption *opt = otCoapOptionIteratorGetFirstOption(&iterator);
opt != NULL;
opt = otCoapOptionIteratorGetNextOption(&iterator))
{
if (opt->mNumber == OT_COAP_OPTION_DESIREDOPTION)
{
doSomethingWith(opt);
break;
}
}
```
or its equivalent. In cases where multiple options are sought, the
above is still the better approach (using a `switch` statement), but
otherwise this seems repetitive.
`otCoapOptionIteratorGetFirstOptionMatching` basically implements the
above loop, returning the first option that matches. This same code can
also be used for testing for presence too by comparing the resultant
pointer to the `NULL` singleton.
For completeness in cases where a software developer may want to
*resume* looking for further matching options,
`otCoapOptionIteratorGetNextOptionMatching` is also provided.
* [coap] Add function for decoding Uint options
To properly implement features such as RFC7641 and RFC7959, one must be
able to decode unsigned integer options. It seems silly to have an
encoder but not a decoder, so here is the missing piece.
Future considerations may be a `uint32_t` version since the majority of
use cases do not call for a `uint64_t`; the type was chosen since it was
the most general case.
* [coap] Reference the RFC for the Observe option.
For the sake of completeness, reference the relevant RFC in the
documentation so that developers can read up more on how it should work.
* [coap] Add mObserve flag to metadata.
When we're implementing observations, a few exceptions to the usual CoAP
conventions apply, notably:
- when we receive the initial reply, the request is _not_ finished, we
will receive subsequent requests until we tell the server to stop.
- when we send a confirmable notification, the observing client will
reply with an `ACK` with no further traffic: we need to differentiate
this from an empty `ACK` to a request meaning we should expect a reply
later.
While we could just repeatedly scan for the `Observe` option on the
request, that is wasteful in terms of processing time. This allows us
to scan the request just once, and set a single-bit flag to save
processing later.
* [coap] Don't expect traffic after ACK to notification
If the client sends an empty `ACK` in reply to a confirmable RFC7641
notification, we will _NOT_ see further replies with additional
information. Consider the notification as acknowledged and pass the
details back to the application.
* [coap] Pass through notifications to handler
Ensure that all notifications sent by the CoAP server are passed back to
the application's CoAP request handler. When we receive the first one,
the request continues -- it does not finish until the server drops the
`Observe` flag or we tell the server to stop.
* [coap] Disable expiry timer once subscribed
If a request carries `Observe=0` and we receive the first reply, ignore
future time-out events as the transaction will finish only when
explicitly terminated.
* [coap] Support eager subscription cancellation
A CoAP client may signal its intention to unsubscribe from a resource by
sending a `GET` request with a token matching that of the original
request and setting the `Observe` option to the value 1.
In the event the application does this, we should pick this up and
cancel the pending transaction locally. The handler will be notified of
this by a final call where `aError=OT_ERROR_NONE` and no message given.
* [cli] Add RFC7641 support to CLI example
As an aide to those developing code that uses RFC7641, implement an
example client and server that supports this feature.
This commit is contained in:
@@ -38,6 +38,7 @@ import simulator
|
||||
import socket
|
||||
import time
|
||||
import unittest
|
||||
import binascii
|
||||
|
||||
|
||||
class Node:
|
||||
@@ -1015,6 +1016,182 @@ class Node:
|
||||
self.send_command(cmd)
|
||||
self._expect('Done')
|
||||
|
||||
def coap_cancel(self):
|
||||
"""
|
||||
Cancel a CoAP subscription.
|
||||
"""
|
||||
cmd = 'coap cancel'
|
||||
self.send_command(cmd)
|
||||
self._expect('Done')
|
||||
|
||||
def coap_delete(self, ipaddr, uri, con=False, payload=None):
|
||||
"""
|
||||
Send a DELETE request via CoAP.
|
||||
"""
|
||||
return self._coap_rq('delete', ipaddr, uri, con, payload)
|
||||
|
||||
def coap_get(self, ipaddr, uri, con=False, payload=None):
|
||||
"""
|
||||
Send a GET request via CoAP.
|
||||
"""
|
||||
return self._coap_rq('get', ipaddr, uri, con, payload)
|
||||
|
||||
def coap_observe(self, ipaddr, uri, con=False, payload=None):
|
||||
"""
|
||||
Send a GET request via CoAP with Observe set.
|
||||
"""
|
||||
return self._coap_rq('observe', ipaddr, uri, con, payload)
|
||||
|
||||
def coap_post(self, ipaddr, uri, con=False, payload=None):
|
||||
"""
|
||||
Send a POST request via CoAP.
|
||||
"""
|
||||
return self._coap_rq('post', ipaddr, uri, con, payload)
|
||||
|
||||
def coap_put(self, ipaddr, uri, con=False, payload=None):
|
||||
"""
|
||||
Send a PUT request via CoAP.
|
||||
"""
|
||||
return self._coap_rq('put', ipaddr, uri, con, payload)
|
||||
|
||||
def _coap_rq(self, method, ipaddr, uri, con=False, payload=None):
|
||||
"""
|
||||
Issue a GET/POST/PUT/DELETE/GET OBSERVE request.
|
||||
"""
|
||||
cmd = 'coap %s %s %s' % (method, ipaddr, uri)
|
||||
if con:
|
||||
cmd += ' con'
|
||||
else:
|
||||
cmd += ' non'
|
||||
|
||||
if payload is not None:
|
||||
cmd += ' %s' % payload
|
||||
|
||||
self.send_command(cmd)
|
||||
return self.coap_wait_response()
|
||||
|
||||
def coap_wait_response(self):
|
||||
"""
|
||||
Wait for a CoAP response, and return it.
|
||||
"""
|
||||
if isinstance(self.simulator, simulator.VirtualTime):
|
||||
self.simulator.go(5)
|
||||
timeout = 1
|
||||
else:
|
||||
timeout = 5
|
||||
|
||||
self._expect(
|
||||
r'coap response from ([\da-f:]+)(?: OBS=(\d+))?'
|
||||
r'(?: with payload: ([\da-f]+))?\b',
|
||||
timeout=timeout)
|
||||
(source, observe, payload) = self.pexpect.match.groups()
|
||||
source = source.decode('UTF-8')
|
||||
|
||||
if observe is not None:
|
||||
observe = int(observe, base=10)
|
||||
|
||||
if payload is not None:
|
||||
payload = binascii.a2b_hex(payload).decode('UTF-8')
|
||||
|
||||
# Return the values received
|
||||
return dict(source=source, observe=observe, payload=payload)
|
||||
|
||||
def coap_wait_request(self):
|
||||
"""
|
||||
Wait for a CoAP request to be made.
|
||||
"""
|
||||
if isinstance(self.simulator, simulator.VirtualTime):
|
||||
self.simulator.go(5)
|
||||
timeout = 1
|
||||
else:
|
||||
timeout = 5
|
||||
|
||||
self._expect(
|
||||
r'coap request from ([\da-f:]+)(?: OBS=(\d+))?'
|
||||
r'(?: with payload: ([\da-f]+))?\b',
|
||||
timeout=timeout)
|
||||
(source, observe, payload) = self.pexpect.match.groups()
|
||||
source = source.decode('UTF-8')
|
||||
|
||||
if observe is not None:
|
||||
observe = int(observe, base=10)
|
||||
|
||||
if payload is not None:
|
||||
payload = binascii.a2b_hex(payload).decode('UTF-8')
|
||||
|
||||
# Return the values received
|
||||
return dict(source=source, observe=observe, payload=payload)
|
||||
|
||||
def coap_wait_subscribe(self):
|
||||
"""
|
||||
Wait for a CoAP client to be subscribed.
|
||||
"""
|
||||
if isinstance(self.simulator, simulator.VirtualTime):
|
||||
self.simulator.go(5)
|
||||
timeout = 1
|
||||
else:
|
||||
timeout = 5
|
||||
|
||||
self._expect(r'Subscribing client\b', timeout=timeout)
|
||||
|
||||
def coap_wait_ack(self):
|
||||
"""
|
||||
Wait for a CoAP notification ACK.
|
||||
"""
|
||||
if isinstance(self.simulator, simulator.VirtualTime):
|
||||
self.simulator.go(5)
|
||||
timeout = 1
|
||||
else:
|
||||
timeout = 5
|
||||
|
||||
self._expect(
|
||||
r'Received ACK in reply to notification '
|
||||
r'from ([\da-f:]+)\b',
|
||||
timeout=timeout)
|
||||
(source,) = self.pexpect.match.groups()
|
||||
source = source.decode('UTF-8')
|
||||
|
||||
return source
|
||||
|
||||
def coap_set_resource_path(self, path):
|
||||
"""
|
||||
Set the path for the CoAP resource.
|
||||
"""
|
||||
cmd = 'coap resource %s' % path
|
||||
self.send_command(cmd)
|
||||
self._expect('Done')
|
||||
|
||||
def coap_set_content(self, content):
|
||||
"""
|
||||
Set the content of the CoAP resource.
|
||||
"""
|
||||
cmd = 'coap set %s' % content
|
||||
self.send_command(cmd)
|
||||
self._expect('Done')
|
||||
|
||||
def coap_start(self):
|
||||
"""
|
||||
Start the CoAP service.
|
||||
"""
|
||||
cmd = 'coap start'
|
||||
self.send_command(cmd)
|
||||
self._expect('Done')
|
||||
|
||||
def coap_stop(self):
|
||||
"""
|
||||
Stop the CoAP service.
|
||||
"""
|
||||
cmd = 'coap stop'
|
||||
self.send_command(cmd)
|
||||
|
||||
if isinstance(self.simulator, simulator.VirtualTime):
|
||||
self.simulator.go(5)
|
||||
timeout = 1
|
||||
else:
|
||||
timeout = 5
|
||||
|
||||
self._expect('Done', timeout=timeout)
|
||||
|
||||
def coaps_start_psk(self, psk, pskIdentity):
|
||||
cmd = 'coaps psk %s %s' % (psk, pskIdentity)
|
||||
self.send_command(cmd)
|
||||
|
||||
Reference in New Issue
Block a user