covertness / coap-rs Goto Github PK
View Code? Open in Web Editor NEWA Constrained Application Protocol(CoAP) library implemented in Rust.
License: MIT License
A Constrained Application Protocol(CoAP) library implemented in Rust.
License: MIT License
The observer support today is a very useful start however it's missing a few key APIs to customize it to work similar to other CoAP implementations and, more importantly, generalized to the use cases described in RFC 7641. For example, the support requires that PUT request be issued in order for the system to work, even though we wouldn't normally expect read-only endpoints to have such a feature. Further, the implementation is just a limited replaying of the last PUT payload without any of the handling consideration that you'd get with a GET request (like if the GET response included ContentFormat options, the resulting notification for this resource path would not include that information).
I believe this can be addressed relatively easily but the easy way will break a few implementation assumptions and change the public API. For those reasons I wanted to open a dialog to make sure my assumptions are correct and my goals are consistent with the projects before proceeding to a pull request. Specifically, the following changes should make this possible:
server.resource_changed(path)
.server.resource_changed(path)
. This response would be cached instead and copied to all observing clients.What do you think of these changes? Are you interested in me proceeding with a possible PR?
Thanks for the great project, this is really helping me get a cool project of mine off the ground using an ultra low powered old Lego EV3 brick as the hub!
CoAP URIs distinguish between coap://foo/path/parts
and coap://foo/path/parts/
(as does any other type of URI). While the former is encoded in two CoAP Uri-Path options ("path", "parts"), the latter is encoded in three ("path", "parts", "") with the third having zero length.
The .set_path()
/ .get_path()
method pair appears to drop the distinction (set_path("/path/parts/")
followed by get_path()
returns "/path/parts"), indicating that the distinction is not made in the coap-rs library.
Looking at the code, it appears that it is sufficient to drop the if-is-empty check for components, though things might still need special treatment then to avoid inserting an empty component at the beginning when a path is passed in with a leading slash.
Hello,
I have a case when remote modem-connected client does not hear response to its latest request, so it retransmits the request, with the same MID.
I thought this is handled in coap-rs library server code, but it seems it's not.
What's the usual solution to this - when request with the same MID is received from the same endpoint?
Should my (user) code on server side just re-execute the request handler again producing new response, or should it cache previous response and just resend the response?
I need clarification on this.
Thanks.
Hello and thanks for your great library. I managed to extend the provided example client and server with some features. One of them is setting the correct CoAPOption::ContentFormat
header.
I managed to do it by consulting RFC7252 and by using the following code snippet:
fn set_content_type(resp: &mut CoAPResponse, content_type: u16) -> () {
let msb = (content_type >> 8) as u8;
let lsb = (content_type & 0xFF) as u8;
let content_format: Vec<u8> = vec![msb, lsb];
resp.add_option(CoAPOption::ContentFormat, content_format);
}
fn main() {
// ...
let content_format = 42; // octet-stream, cf. RFC7252
set_content_type(&mut response, content_format);
}
However I think there is an abstraction to bring here. The first step is to create an enum for all the available registered format types, like this:
enum ContentFormat {
TextPlain,
ApplicationLinkFormat,
ApplicationXML,
ApplicationOctetStream,
ApplicationEXI,
ApplicationJSON
}
fn content_format_id(content_format: ContentFormat) -> u16 {
match content_format {
ContentFormat::TextPlain => 0,
ContentFormat::ApplicationLinkFormat => 40,
ContentFormat::ApplicationXML => 41,
ContentFormat::ApplicationOctetStream => 42,
ContentFormat::ApplicationEXI => 47,
ContentFormat::ApplicationJSON => 50,
}
}
And then we can think of helpers like a set_content_format
method.
What do you think?
The CoAPServer::handle
takes a 'static
handler.
I'm a bit new to rust, but I believe this means we can only use top level methods as handler, and then all state needed for the server will need to be stored in global variables?
As far as I understand it, this will also make the routing tool in #4 impossible.
Please correct me if I'm wrong!
Hi,
I'm hoping you can help. I'm not sure if what I've found is an issue, or if I'm using your coap-rs implementation incorrectly.
I'm also new to Rust and CoAP, so hopefully my question and code are not too painful...
My use case:
My obstacle:
A simplified version of my client and server programs are attached:
CoapTesting.zip
A code snippet taken from my server code is below and this illustrates:
` fn main() {
let addr = "127.0.0.1:5683";
Runtime::new().unwrap().block_on(async move {
let mut server = Server::new(addr).unwrap();
println!("{} Server up on {}", now_time_str(), addr);
server
.run(|request| async {
log_request(&request);
let payload_data: String;
match request.get_method() {
&Method::Get => {
match handle_get(&request) {
Ok(data) => {
payload_data = data;
},
Err(e) => {
payload_data = format!("Error: {}", e);
}
}
},
&Method::Post => {
match handle_post(&request) {
Ok(data) => {
payload_data = data;
},
Err(e) => {
payload_data = format!("Error: {}", e);
}
}
},
&Method::Put => {
match handle_put(&request) {
Ok(data) => {
payload_data = data;
},
Err(e) => {
payload_data = format!("Error: {}", e);
}
}
},
_ => {
payload_data = "method not supported".to_string();
},
};
return match request.response {
Some(mut resp) => {
resp.message.payload = payload_data.into();
log_response(&resp);
Some(resp)
}
_ => None,
};
})
.await
.unwrap();
});
}`
` fn handle_get(request: &CoapRequest) -> Result<String, String> {
println!("{} Handling GET: {}", now_time_str(), request.get_path());
if let Some(observe_option) = request.get_observe_flag() {
// Handle observe register/deregister
match observe_option {
Ok(observe_value) => {
match observe_value {
ObserveOption::Register => {
// To do..
},
ObserveOption::Deregister => {
// To do..
},
}
},
Err(_) => {
// Handle unknown observe value
return Err("Invalid observe option".to_string());
}
}
}
// Handle different GET scenarios...
return Ok("Some Data".to_string());
}`
Thanks
CoAPClient::observe
uses the hard-coded default receive timeout of 2 seconds, instead of the timeout set by a prior call to set_receive_timeout
.
The ContentFormat enum currently only supports the six content formats defined in RFC7252.
Please update that list to the latest IANA published state or allow extension (eg. Other(u16)
). No further support by the CoAP library beyond extending the list is required; applications need to then parse their respective formats themselves anyway, but currently can't set a content-format not in the list from 2014.
My particular application will need the application/cbor
and application/senml+cbor
types, but it probably makes sense to batch-import all the currently registered types.
Looks examples works fine under 2021 edition.
For some applications it's necessary to have access to the sender from the callback. It would be useful to have the SocketAddr
in the handler signature.
Is there any plan to support no_std with this crate since CoAP is heavily used in embedded? There is coap-lite but it's unmaintaned and it handles only packet parsing and creation
It appears that the underlying IO library is asynchronous, and the server implementation does indeed expose an asynchronous handler interface.
It would be nice if the client side could also be made to have an asynchronous interface rather than having to do a blocking read.
Very happy to se coap implemented in rust.
I am trying to communicate with my IKEA tradfri gateway over the coap-protocol.
The request i am trying to do is to mimic this terminal command:
coap-client -m post -u "Client_identity" -k "$GATEWAYCODE" -e '{"9090":"$USERNAME"}' "coaps://$GATEWAYIP:5684/15011/9063"
My understanding from reading https://github.com/obgm/libcoap/:
-e is the payload
-k is supposed to add a key to the header
-u is adding a user to the header
But when i try to use the example_post() code in the gateway does not even respond with a status code.
As you can see in the terminal command above the request is to 'coaps:://' and not 'coap::// which im thinking might be where my problem lies. I have not managed to find where this is set in the source of coap-rs.
In addition to this i cant seem to find the best way to mimic adding a key or user to the header from looking at the source of coap-rs.
Anybody that could enlighten me? Would be much appreciated.
So by default CoAPClient::new()
binds to 0.0.0.0:0
for Ipv4 and :::0
for Ipv6.
I have the need to use Ipv6's scope_id
when binding, so I can specify which interface I want the packet to go out on. SocketAddrV6::new()
lets me specify the scope_id
I want to use, but the current CoAPClient assumes I want :::0
. Allowing the user to pass in another ToSocketAddrs
for the bind address would alleviate this issue.
I have forked the repo and made the change so I can continue working, but it'd be cool if the main repo had support for it. Change is here: robbym@4fa038d
To prevent API breakage, maybe a different function that takes both the bind address and destination address?
Currently, coap-rs never sets the Uri-Host option. This behavior is independent of the value of the host portion of the URI passed in.
If a URI is decomposed into options and the host component (domain
in the source) is not an IP literal, it would be turned into a Uri-Host option according to RFC 7252 Section 6.4 item 5.
Not doing this becomes an issue for clients that interact with servers that do name based virtual hosting, as is often the case with reverse proxies as employed in NAT traversal.
As many systems do not require this option even when the IP address is resolved through DNS, it might be practical to offer methods like .post()
both in a variant that sets and one that does not set the Uri-Host. (Also, different people have different opinions of how optional it is to send it, the CoAP wiki has both sides (disclaimer: I wrote that)).
(CC @malishav with whom I encountered this).
Are there any plans / ideas / thoughts around implementation of Blockwise transfer for Block1 type of requests?
I can help with the implementation.
This is related to an issue with mio.
This issue is related to tokio-rs/mio#411
This is more of a question, rather than an issue, but curious if there are any plans to support TCP RFC (https://datatracker.ietf.org/doc/rfc8323/) for coap-rs.
The CoAP spec (RFC 7252) allows for options using the uint type to be encoded as an empty vector for 0 but this crashes here:
Line 85 in 69e40da
coap-lite upstream has been upgraded to have proper handling for these types and we can fix the issue by simply upgrading to 0.8 (PR incoming)
It would be nice to have a tool to handle request routing. One good example is Iron's router
functionality. This prevents every user from having to reimplement this functionality.
https://www.rfc-editor.org/rfc/rfc7641.txt
i don't find any fonction to use this feature inside this lib.
I need this one for a project, do you have a idear to implement it ?
Versions of Tokio >=1.34 and higher require Mio >=1.8.9 and causes a version conflict when compiling as coap uses 1.8.8.
A semver bump may resolve this unless there are other aspects that this introduces that would need testing first?
From looking at the example and observer code, it seems that the only way to modify a resource and trigger an observe notification is for a client to PUT new data.
I have many CoAP usage examples which call for an observable resource that is only modifiable by the server (e.g. server exposing a temperature sensor resource). This is doable in other CoAP libraries (e.g. Lobaro, CoAPthon) but not this one.
I think that the resource modification and notify should be decoupled from client requests entirely, and be up to the server implementation to trigger a resource update when required.
Using CoAPClient
, there is no way to set options in a request. It's possible to do so by constructing a CoapRequest
manually, but to do so requires re-implementing a bunch of functionality already present in CoAPClient
(gen_message_id
, request_path_with_timeout
and parse_coap_url
in particular).
It would be good to have the ability to add a custom option.
A quick and dirty way could be to add to the CoapOption Enum: Custom(u8)
coap-rs
depends on an ancient version of rust-url
(v0.2.36 of Jul 2015) that appears to have issues with IPv6 addresses in URLs. Upgrading this dependency needs to take care of API changes, however. (Sadly, I'm too much of a Rust noob still to do this myself and offer a PR.)
Anyway, the effect is that coap-rs
will choke on URLs such as "coap://[bbbb::9329:f033:f558:7418]/.well-known/core"
with a "domain error".
I'd like to request a feature where once the coap-rs Server is running, the program that calls server.run() can inject/queue a notification packet for the Server to send. If the notification that was injected/queued is confirmable, then the confirmation response can be exposed via the same mechanism that other requests are exposed (e.g. via the server.run() closure).
This feature follows on from the disable_observe_handling() feature that I'm super grateful you implemented so quickly.
To summarise how and why I'm looking to use this new feature...
Note: Initially, I had hoped that 'Program B' could send a notification packet to the observer itself, but I've found that this doesn't work, and I suspect it is because the Coap Client only accepts notifications from the Coap Server's IP:Port and not from a different IP:Port source.
Below is some prototype code that shows how I'm currently using coap-rs Server. I have some 'TO DO' comments where I could conceivably implement the new feature I'm requesting.
`fn run_server(server_settings: ServerSettings) {
let running = Arc::new(AtomicBool::new(true));
let running_setup = running.clone();
let running_monitor = running.clone();
// Set up the signal handler
ctrlc::set_handler(move || {
running_setup.store(false, Ordering::SeqCst);
}).expect("Error setting Ctrl-C handler");
let rt = Runtime::new().unwrap();
rt.block_on(async move {
let server_addr = server_settings.uri.clone();
let mut server = Server::new_udp(server_addr).unwrap();
server.disable_observe_handling(true).await;
println!("Server up on {}", server_settings.uri.clone());
// Spawn the CoAP server as a separate task
let server_task = tokio::spawn(async move {
server.run(move |request| {
let running_copy = running.clone();
let server_settings_copy = server_settings.clone();
async move {
let result = handle_request(request, running_copy, &server_settings_copy).await;
if let Some(ref response) = result.response {
log_response(&response, &server_settings_copy);
}
// TO DO.. If the notify_waiting flag is set, then build the
// notification and pass it to server to internally queue and send when able
return result;
}
}).await.unwrap();
});
// TO DO.. Spawn a task that monitors a Redis notify_queue and if an entry is found
// toggle a notify_waiting flag within server_task.
// Detect and handle the program shutdown signal
while running_monitor.load(Ordering::SeqCst) {
tokio::time::sleep(Duration::from_secs(2)).await;
}
println!("Shutting down");
server_task.abort();
});
}`
What do you think? Is my feature request doable/useful? Or is there a better way to do what I'm attempting to do?
The crate metadata reference the documentation stored on http://covertness.github.io/coap-rs/master/coap/index.html. That doesn't tell its exact version, but quick bisection shows it's at a roughly February 2020 state.
Please either continue updating that documentation, or just change the link to the automatically built documentation at https://docs.rs/coap/.
When used as a CoAP client, the token is always empty, and the message ID is not set.
This does not cause practical problems as long as they are sent from per-request sockets (as they are in the client example), but if multiple requests are sent to the same client, for example as in
let mut client = CoAPClient::new("localhost:5683").unwrap();
client.request_path("/time", coap_lite::RequestType::Get, None, None).unwrap();
client.request_path("/location", coap_lite::RequestType::Get, None, None).unwrap();
then a CoAP server that does request deduplication will respond to the second request with the time of the first request, rather than with its location.
(CC @malishav with whom I encountered this).
The 0.12.2 version updated the coap-lite dependency from 0.9 to 0.11. That update is semver incompatible because some coap APIs (eg. post_with_timeout) implicitly re-export coap-lite types.
The only semver compatible way out of this is yanking the 0.12.2 version, and re-publish as 0.13.0. Otherwise, acknowledging that this happened and will not be yanked is also fine with me (most dependencies that are affected can probably work around it by depending on ^0.12.2 and coap-lite 0.11) -- in which case I'd just like to ask for more careful re-exporting in future updates.
Hi,
I am proprosing the following:
coap-rs should recense every implemented IETF standards (i.e. draft, proposed standard rfc and possibly informational standard rfc that updates RFC 7252 Standard) onto the project about section in the topics like on plgd-dev/go-coap
If you have any other standards to suggest, please let a comment.
* DTLS 1.2 is very important to the core standard of CoAP. Please if you are looking for a bounty, I suggest to look for this one. You can even create your own crate on doc.rs and we will help you implementing this.
Hi,
I'm trying to receive multicast coap messages from a thread network.
This network is available via wpan0 and has ipv6 addresses.
I'm trying the server demo, with the following modification:
let addr = ":::0";
Unfortunately, I do not receive any packages.
What could be wrong?
Things I've tried:
Thanks.
Some products (e.g. IKEA Tradfri) use COAP over DTLS (TLS over UDP). It would be great to have the library support this use case.
See
Hi!!! I want to use CoAP-rs for voice assistant components, and we need to use observe, however, there's no support for updating the resources from the server anymore and trying to access the same server from a client in the same process (as a workaround) throws:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }'
Is server-side resource update planned again, or at least, can the workaround be made to work in the meantime?
A CoAP server has the choice to either send the response message to a confirmable request as a piggy-backed response (along with the ACK), or as a separate message. When coap-rs is used in a client and the server sends a separate message, the empty ACK is given to the application, where I'd expect the library to give the application the actual response (which is sent later).
If this is not handled in the CoAP library, client applications can easily start breaking once a server is updated to a newer version (that might make better informed decisions on when to send which kind of response), when proxies are in use, or simply when the server is under higher load than usual. I do not think that it is reasonable to expect application code to handle this manually, given the overall high level of abstraction coap-rs provides. Depending on how #79 is implemented, applications might not even have the means to implement the necessary behavior on their own.
RFC 7252 has the precise details, but the rough logic I'm using in other implementations is:
(CC @malishav with whom I encountered this).
The last version coap-lite = "0.11.3"
contains an important fix for block-wise transfer: martindisch/coap-lite#31
coap-rs
currently depends on coap-lite = "0.9.1"
. Also, it might be better not to depend on a strict minor version.
Line 175 in 76bea97
The referenced unwrap()
causes a panic based on the current network connectivity situation of the host. This is an external situation w.r.t. to the line in question (not a programmer error) and so an Result::Err(_)
should be raised instead.
Hi. Thank you very much for this crate. Is this ready for production use?
I recently hit a case where I had too large of a response payload which resulted in a quite confusing error as it returned from server.run()
only to panic based on server.run(...).await.unwrap()
:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: InvalidData, error: "CoAP error: invalid packet length" }', src/main.rs:69:14
I understand we don't have Block1/2 (RFC 7959) support, however we should be able to do better than "crash" the server in this case. At a minimum I think we want to make sure I/O errors during server.run()
don't return for every customer since that would easily allow for a DoS attack. Maybe just isolate self.server.send(...)
errors and log them instead of returning?
Out of curiosity as well, what would it take to actually implement RFC 7959 support? It seems like we'd need to do some kind of caching by (Token,Endpoint) for some period of time and then add some request/response handling to add/interpret Block2 options accordingly when serving our cached result. Shouldn't be too bad though, right? I might take a crack at a generic implementation downstream of coap-rs and see if it can't be ported back into the core if there's interest.
I am looking at building this for Linux however I am getting the following error:
error: failed to build archive: Text file busy
error: aborting due to previous error
error: Could not compile `winapi-build`.
This is using the minimal example provided.
It appears that the only way to construct a client request without manually splitting the URI (ie. re-implementing parse_coap_url) is using the get
method, which precludes the use of different request methods.
Please document how a request POST or PUT message, with possible additional options, is supposed to be created from a URI.
I'm experimenting and when this as a server and Python's aiocoap as a client I get a crash:
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 0', /home/sergio/.cargo/registry/src/github.com-1ecc6299db9ec823/coap-0.11.3/src/observer.rs:85:59
import asyncio
from aiocoap import *
async def main():
protocol = await Context.create_client_context()
request = Message(code=GET, uri='coap://127.0.0.1:5683/hello/put', observe=0)
pr = protocol.request(request)
r = await pr.response
print("First response: %s\n%r"%(r, r.payload))
if __name__ == "__main__":
asyncio.run(main())
Changing the "observe" flag to something other than 0 doesn't crash, however, the resource is not observed.
EDIT: On second thought this looks more like an issue in coap-lite
isn't it?
I've been trying to add a channel to the coapserver message handler, but failing
This is as far as I get:
use coap::{CoAPRequest, CoAPResponse, CoAPServer};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn request_handler(request: CoAPRequest, channel_tx : Sender<u8>) -> Option<CoAPResponse> {
None
}
fn main() {
let addr = "[::]:5683";
let mut server = CoAPServer::new(addr).unwrap();
let (channel_tx, channel_rx): (Sender<u8>, Receiver<u8>) = mpsc::channel();
server.handle( move |request| {
request_handler(request, channel_tx.clone())
}).unwrap();
}
Errors when building:
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
--> src/main.rs:18:20
|
18 | server.handle( move |request| {
| ------ ^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `Fn`
| |
| the requirement to implement `Fn` derives from here
19 | request_handler(request, channel_tx)
| ---------- closure is `FnOnce` because it moves the variable `channel_tx` out of its environment
error[E0277]: `std::sync::mpsc::Sender<u8>` cannot be shared between threads safely
--> src/main.rs:18:12
|
18 | server.handle( move |request| {
| ^^^^^^ `std::sync::mpsc::Sender<u8>` cannot be shared between threads safely
|
= help: within `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`, the trait `std::marker::Sync` is not implemented for `std::sync::mpsc::Sender<u8>`
= note: required because it appears within the type `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`
= note: required because of the requirements on the impl of `coap::server::CoAPHandler` for `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`
error[E0277]: the trait bound `std::sync::mpsc::Sender<u8>: std::marker::Copy` is not satisfied in `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`
--> src/main.rs:18:12
|
18 | server.handle( move |request| {
| ^^^^^^ within `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`, the trait `std::marker::Copy` is not implemented for `std::sync::mpsc::Sender<u8>`
|
= note: required because it appears within the type `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`
= note: required because of the requirements on the impl of `coap::server::CoAPHandler` for `[closure@src/main.rs:18:20: 20:6 channel_tx:std::sync::mpsc::Sender<u8>]`
error: aborting due to 3 previous errors
Hello,
I know this library doesn't support Observe RFC yet. Does it support Blockwise Transfer (RFC7959) and Link Formats (RFC6690)?
Besides those, which other options are NOT supported?
Thanks!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.