-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
net: lwm2m: Add support for blockwise GET and FETCH #61858
net: lwm2m: Add support for blockwise GET and FETCH #61858
Conversation
0fd93e9
to
21c418a
Compare
@LukasWoodtli please review also. You wrote the initial blockwise-send implementation. |
For those interested: Here is a Wireshark capture of blockwise GET (LwM2M read), Blockwise FETCH (LwM2M composite read), and blockwise POST (LwM2M send)
For the testing, I modified BinaryApp container to have 5 kB of buffer in resource |
After doing some reading (thanks for the wireshark log) it seems to me that indeed there is some gap between Block transfer RFC and FETCH/PATCH RFC. RFC7959 indeed says:
Which to my understanding leaves the option to change the token for each exchange. This may work fine for block transfers of individual resource, as that URI is retransmitted in each request, so one could identify the block context based on this information. But for FETCH? I see in the logs that the payload (carrying information about requested resources) is only sent in the initial block request. But for the consecutive blocks, we only have CoAP header, and w/o URI options there is no other way to identify the request other than the token.
If it was only about the "freshness" of the content, I guess this could be handled by the Etag option. But with FETCH I think it's actually worse than you describe - if you get a request for consecutive block with a different token, there is literally no way to tell what the content should be (i. e. what resources to include in the reply). |
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.
Overall looks good.
return -ENOENT; | ||
} | ||
|
||
if (msg->path.level < 3) { |
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.
Not sure if I understand this check anyway, wouldn't lwm2m_engine_get_create_res_inst()
fail if the path level was lower than LWM2M_PATH_LEVEL_RESOURCE
?
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.
This is copy&paste from existing rw_plaintext.c. I don't know the logic behind it, so I did not want to touch it.
This has confused me for a while now. It seems that, using the token to match blocks that belong to the same message, is not intended by CoAP. But there doesn't seem to be a commonly accepted solution for that. |
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.
LGTM
} | ||
|
||
/* Check for block transfer */ | ||
block_opt = coap_get_option_int(msg->in.in_cpkt, COAP_OPTION_BLOCK1); |
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.
I would use coap_get_block1_option
here.
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.
This block of code is not written by me. Just moved from https://github.com/zephyrproject-rtos/zephyr/blob/main/subsys/net/lib/lwm2m/lwm2m_message_handling.c#L2253C1-L2312
So I did not refactor it.
} | ||
|
||
/* Handle blockwise 1 (Part 2): Append BLOCK1 option / free context */ | ||
if (block_ctx) { |
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.
How about error checking? r
could be overwritten by coap_append_block1_option(msg->out.out_cpkt, &block_ctx->ctx);
further down without being checked for an error.
@@ -2667,11 +2745,23 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, uint8_t *buf, uint16_t buf_ | |||
lwm2m_reset_message(msg, true); | |||
} | |||
|
|||
clear_ongoing_block_tx(); |
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.
What if there are multiple parallel block-wise transfers in progress. Aren't they all cleared here?
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.
This is intentional.
I don't believe any server would do parallel block-wise transfers with a single client.
I cannot be sure when the server is finished with the blockwise GET because I cannot keep any timeout. Server is supposed to retry. So I drop all ongoing block transfers when I receive a request that is not part of any ongoing operation.
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.
It bugs me whether we should differentiate between client-initiated BLOCK1 transfers (LwM2M Send) and server-initiated BLOCK2 transfers (LwM2M Get/Fetch). I think there is no ambiguity between those two. And right now it seems that we'd abort an ongoing block Send on reception of any (even non-block) request/response from the server. And conversely, the application can hijack any ongoing BLOCK2 transfer when calling lwm2m_send_cb()
.
OFC we can agree on such a limitation, though it doesn't seem too difficult to avoid that.
21c418a
to
9091968
Compare
No, obvious an implementation, which requires to reuse tokens in a blockwise transfer is not compliant. |
It is not. Requests containing blocks of the same operation are identified by having the same set of options (except obviously the block options), not their tokens. If this relies on the tokens to be common, it will only work with selected CoAP libraries that make this particular choice in their requests (such as Californium with the peculiar COAP.BLOCKWISE_REUSE_TOKEN=true option), but will not work with general CoAP libraries. There is ongoing discussion in the CoRE working group (in its public meetings, possibly on its mailing list and now also in core-wg/corrclar#29) about enhancing things in block-wise, but to my knowledge there is no good reason to use this behavior -- moreover, all participants I've spoken to in those disucssions were clear that relying on the token is not how it should be done, some being as explicit as stating that "if people start relying on Californium producing identical tokens, they are effectively forking the CoAP ecosystem". I'm not too deeply familiar with LwM2M, and only used Zephyr once or twice, so please cut me some slack in understanding the full changes here. But please, if you think you need identical tokens, let's discuss why you need them (here or over in core-wg/corrclar#29), and let's work out how this is done in a way that it works with all CoAP peers. |
To be clear: The intention of
is not to manifest that wrong usage. It's about providing a work-around for the meantime until an implementation complies to RFC7959. |
0ab2699
to
e06b06e
Compare
Rebased the implementation. I also refactored the Just to be on a safe side, I added a support for using Etag option so a client can notice if the content have changed between blocks. As a Etag, I just use |
Thanks for the update, both on the token and the ETag side.
To clarify: To a server, Request-Tag is not special. If the client used option 65316 (from the experimental range, and has the same relevant bits set) instead of Request-Tag, everything Request-Tag is there for must keep working just the same. The ETag is good and necessary even without (intentionally) concurrent requests. |
if (ret < 0) { | ||
return ret; | ||
} | ||
sys_slist_append(&ongoing_block_sends, &msg->node); |
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.
We're effectively pushing the same sys_snode_t msg->node
to two lists, ongoing_block_sends
here and pending_sends
there. This doesn't seem correct.
Since we only support a single outgoing BLOCK2 transfer, perhaps just store the pointer (exclusively for BLOCK2 transfers)?
@@ -2667,11 +2745,23 @@ void lwm2m_udp_receive(struct lwm2m_ctx *client_ctx, uint8_t *buf, uint16_t buf_ | |||
lwm2m_reset_message(msg, true); | |||
} | |||
|
|||
clear_ongoing_block_tx(); |
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.
It bugs me whether we should differentiate between client-initiated BLOCK1 transfers (LwM2M Send) and server-initiated BLOCK2 transfers (LwM2M Get/Fetch). I think there is no ambiguity between those two. And right now it seems that we'd abort an ongoing block Send on reception of any (even non-block) request/response from the server. And conversely, the application can hijack any ongoing BLOCK2 transfer when calling lwm2m_send_cb()
.
OFC we can agree on such a limitation, though it doesn't seem too difficult to avoid that.
Partial content format support is required to have a proper support for content format OPAQUE instead of threading it as a part of plain text format. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
Opaque content format is not part of clear-text, so it should be separated into its own file. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
In reality, single-write is the only operation that handles BLOCK1 operations when receiving paylod. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
lwm2m_udp_receive() is only called with same function pointer, so there no need to carry that in the parameter. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
Allow blockwise-send buffers to be used with GET and FETCH queries as well. When outgoing packet is split into multiple blocks, don't free it when first block is send. Keep it in memory until some other requests come. Following queries to next block are matched using CoAP token. However, this required Leshan to use COAP.BLOCKWISE_REUSE_TOKEN=true option from Californium. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
Composite read was incorrectly trying to parse CoAP packet instead of payload of the packet. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
To protect the integrity of outgoing block-wise transfers, append Etag option that allows client to see if the received block is generated from same content as it is expecting. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
Previously each piece of LwM2M SEND was using token length of zero. I think this was unintentional. Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
e06b06e
to
42a30c5
Compare
@rlubos I refactored the PR so that BLOCK2 sends are the only ones that I keep the pointer for. And there is only one ongoing Block2 at a time. When we receive any incoming request, we clear the previous block2 transfer. There is no timeout for this. As long as the server keeps asking for blocks, we keep sending. Block1 transfers (LwM2M SEND and registration) are handled differently. This is the code that Lukas did earlier. CoAP timeouts or ACK on the final block will clear all the state. This is not kept in the same list as Block2. Ending of Block1 does not anymore cause clearing of Block2 transfers. Now all block2 functions are clearly named Now it looks like Block1 and Block2 can happen same time They don't collide, because on is request(CON) and another is ACK. |
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.
Thanks, no more concerns from my side
This PR contains chain of changes to allow CoAP blockwise support for GET and FETCH messages using mechanisms introduced in #54761.
However, the PR mentioned was only working for LwM2M SEND and registration messages. Both which are initiated by LwM2M client and are CoAP queries (CoAP CON).
To support GET and FETCH (Composite READ) I need to add mechanism not to free the response packet until last block is send out, or any other query starts.
I also found a bug in SenML-CBOR content format that did not parse Composite-GET path correctly.
Opaque payload format is now separated onto its own, and not considered as a part of Plain-Text format. This allows follow up development where we can support stateless Blockwise-GET into single binary resources without keeping the full content in the memory. I did not implement this yet, is it would require API changes into the read-callback.
Also, it is notable that I use CoAP Token to find an ongoing blockwise-transfer. This seem to work OK with AVSystem Coiote server. For Leshan this requires
COAP.BLOCKWISE_REUSE_TOKEN=true
option to work.There seem to be discussion regarding Leshan/Californium choice of using different token:
which seem to refer to https://core-wg.github.io/attacks-on-coap/draft-ietf-core-attacks-on-coap.html#name-the-response-delay-and-mism
I don't totally agree their choice. If GET comes with different Token, we could generate a new content for it, but how do you ensure that content is the same than from previous query? In my opinion they have introduced a bug into Californium.
Working GET query using the same Token against Coiote: