-
Notifications
You must be signed in to change notification settings - Fork 2k
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
nanocoap: Allow accessing opaque and other written options #11437
Conversation
The coap_opt_by_index function is added to allow reading and modifying the options written to a message. This is the reading counterpart to a514609.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chrysn, thanks for the PR! It's so helpful to learn what you need from the library.
To summarize, I read that the use cases below are the main concern.
- Read opaque option
- Update value after added
- Iterate over options
For the discussion below, it helps to see nanocoap's function documentation grouped by subject. See #10876, which hopefully will be merged soon.
Reading options
I agree we need a way to read an opaque option. Currently nanocoap has a string reader (coap_opt_get_string()). We also need coap_opt_get_uint(), which can easily be built from coap_get_content_type().
Update value after added
I agree on the usefulness for the ETag example, although it would only work with the gcoap API when writing a message because it requires a coap_pkt_t.
Iterate over options
If OSCORE needs an option iterator, we would want to support that. Also, nanocoap generally looks up options by option number, so it would be nice to support both approaches.
Implementation
The current structure of coap_opt_by_index() accesses an option by index in the list of options. However, coap_parse() only adds a coap_opt_pos_t option for each unique option number. For example, it would only add a single entry for Uri-Query. You can see this effect in your aiocoap-client example. Try adding a second query option to the request, and the output still will show a single 15 key in the output.
How about this idea:
ssize_t coap_opt_get_next(const coap_pkt_t *pkt, coap_opt_pos_t *opt, uint8_t *value)
Let the opt
param be inout. coap_opt_pos_t includes an option number and a pointer to the option. So on the input, it is a qualifier so we can find by option number and allow for iteration. If the option number is zero, start from the first option. Othewise start from the first option for the given option number. If the pointer is not null, start from that as the last option read so we can iterate over multiple options with the same option number.
For the response, the function sets the option number and pointer to the start of the option. If the requested option is not found, return 0. This means the user doesn't need to query pkt.options_len when iterating. I'd like to avoid exposing that to the user if we can.
The pkt and value options would retain the same meaning as in your coap_opt_by_index(), as would the return value as the length of value. This gives us the ability to read an opaque option and enough information to write the value. I also would allow negative values for the return just in case we want to do that in the future.
Other issues with PR
Example error handling -- Is it useful to reply on error? Why not just print an error message?
Thanks for your input -- had I read it earlier I wouldn't have stumbled over the trouble with reading options twice myself just now. I've implemented a
(I'm not sure about whether the implementation is the best there can be, but that's for later review)
I could use a little more explanation here, I both Packet and Buffer API were part of nanocoap (which is only utilized by gcoap) and both use coap_pkt_t, will that not stay so?
The "error" label on the goto was probably a misnomer; it's not so much an error reply but more a "indicate that the result was truncated and continue" way out of options that are too long to display in the buffer. (In general I think it makes sense to provide good error responses via CoAP -- whatever that thing will be tested on, I don't know whether it'll have stdio, but it sure will have network, and whoever requested the result will have an easier time correlating the error response with the request than interpreting stderr). New commits to the PR are coming in, rebasing to master may need some more time unless it's trivial. The changes will need squashing as they'll make little sense like that in the history, does that warrant a "WIP" note in the title? |
See RIOT-OS#11437, to be squashed before merging.
To be squashed before merging.
Brief update: I've just discovered that the present code fails in presence of message payloads due to my misunderstanding of a helper function; please hold reviews until the next update. |
This allows actually testing the error fixed by the previous fixup, as a GET never has a payload but a POST may.
Apologies for the delayed response, @chrysn. I tried to code up a function that incorporates your feedback to my suggestions for coap_opt_get_next(). I ended up creating two functions, coap_opt_get_next() for iteration and coap_opt_get_value() to retrieve an opaque value. See my coap/iterate_and_opaque branch. For coap_opt_get_next(), I agree that it's clunky to require the user to initialize the coap_opt_pos_t, and I also agree that it's more natural for the return value to be the value pointer. nanocoap actually already has a similar internal function, coap_opt_get_string() is the only user of coap_iterate_option(). I plan to replace that use with coap_opt_get_next() and remove coap_iterate_option(). Now that coap_opt_get_next() is optimized for iteration, it made sense to create coap_opt_get_value(), which is optimized to retrieve a particular opaque option. The implementation uses a common implementation pattern for these 'get option' functions -- coap_find_option() followed by _parse_option(). So, I plan to refactor the implementation of coap_get_option_uint() to use coap_opt_get_value(), and rename it to coap_opt_get_uint() and make it public. I also plan to refactor coap_get_blockopt() to use coap_opt_get_value(). If this all sounds good to you, then let's close this PR. I'll create a PR for each of the other functions, and make it a high priority to get this merged so you can use them with the OSCORE integration. |
Yes, the iterate_and_opaque branch looks like it could work. I probably won't use Will you open a PR for that branch so I can leave a proper review there? (And please don't close this PR right away, there's a few things in here I'd like to salvage onto your branch, in particular the test / CLI example, also to make sure I don't miss out the const fix -- I'll close it once they're ported over). |
Sounds good. I'm on it. |
Clearing up what's left of this PR:
All being covered, closing in favor of #12074. |
Contribution description
This is an extension to the API of nanocoap that previously did not give the user a means to iterate over all options, or to look into option that do not fit one of the supported forms (extracting numbers or building a single-character-separated string).
The added function can be used to plainly read option values (eg. in evaluating the ETag options of a request), but also to update options previously written (eg. to set an already allocated ETag, or to find which bit to change when setting the "more" flag on a Block2 response after having built the output). It can also be useful when iterating over all options of a message, eg. to build a cache key or when implementing OSCORE.
No method of iteration is provided per se; it is expected that users of that API will reach into
.options_len
/.options
to obtain the count of options and their stored optionnumber. (That would almost have been sufficient to do what this function offers, but the options values lack the information on where the option actually starts, which is usually obtained by the internal_parse_option
).Testing procedure
The second commit adds a resource to the nanocoap_cli test. In that test, a GET to the /req-opts resource now gives output similar to:
$ aiocoap-client "coap://[fe80::acfc:4bff:fe32:4907%tap0]/req-opts?foo=bar" 11: 7265712d6f707473 15: 666f6f3d626172
Issues/PRs references, impact & status
This has not been discussed in a previous issue/PR, but can be seen as the reading counterpart of #11386 -- in combination, one can reproduce arbitrary options from the request message into the response message (eg. the Echo option).
This change has no impact on the output code of existing programs as it purely adds a method. It increases the API surface of nanocoap, and introduces a concept there that was previously not used much (the index into
pkt->options
) -- it's more common in the internal API to pass around pointers to the start of the option right away.The change is working as it is, but I'm open to suggestions on how the same goal can be achieved more within the style of nanocoap, eg. by not requiring the user to access the pkt struct fields.