Covertness/coap-rs

The observer implementation and my use case

justinh-xacom opened this issue · 7 comments

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:

  • I want to use your coap crate to implement a CoAP server that handles a GET+OBS request from multiple CoAP clients.
  • The server has a resource called 'point' that the client needs to get and maintain a copy of.
  • The client can update 'point' by sending a CoAP PUT to the server.
  • The server needs to notify the client of changes that it makes to 'point', which is why the client previously sent the GET+OBS to register itself as an observer.

My obstacle:

  • The problem I have is that whenever the client sends the GET+OBS request to /point, it gets a 404 response and I have no visibility into where this response is being generated.
  • e.g. I'm logging all request packets passed to the server's request handler, but the GET+OBS registration request is not seen.
  • I want the server to relay the request to the request handler so that I can create the 'point' resource if it doesn't yet exist, and reply to the GET with the 'point' data.
  • Please help me understand why this isn't working as I expect?

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:

  1. The log_request call that never captures the GET+OBS request.
  2. How I was expecting my handle_get function to be able to manage the register and deregister behaviour.

` 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

The way observe currently works is a little wonky sometimes. AFAIK it does the following:

  • when a request is received, we use coap-lite to intercept the request and handle observation in the crate directly
  • updates to a resource are done "automatically" when you put / post to the same resource
  • It may be the case that you get a 404 because observe currently "lazily" keeps track of existing resources only AFTER you interact with them. Please look at the tests under src/observer.rs if you want to see how it currently works (you need to send a PUT request first)
  • also note that the current observe API can result in your futures not waking up properly if you continue to use the client after observing something (you will have 2 futures polling the same socket if you try to send requests after starting to observe something).

It is pretty difficult to get observe to work like it does in something like libcoap without reworking the server API to define Resources instead of a single handler function. Until then, please look at the tests for observer.rs and see if it works for your use-case

Many thanks. That explains the behaviour I'm seeing.

Perhaps consider this a feature request: To add a mechanism/option that allows the coap::Server's auto-observe handling to be disabled, in which case observe requests get passed to the handler instead of being internally handled.

For my use case, I could then take responsibility for managing the list of observers based on source IP:Port and token and also take responsibility for pushing notification packets as needed.

Currently the resource only can be observed after it was created by a PUT request. The resource is updated by sever automatically.
Do you want handle the observe by yourself?

I think so, yes.
For my scenario, I don't want to force the client to do a PUT before doing a GET+OBS.
Consequently, my program needs to detect the GET+OBS request, generate the resource if it doesn't yet exist, reply to the GET and insert/update the observer list.

I'm also considering a distributed approach, where my resources are stored externally (e.g. Redis). With this in mind, the GET/PUT/POST/DELETE operations when handled by the Server get read/written to Redis. I'd then have a separate process that manages the observer notifications.

Okey. I will add an option to enable observe message transparent transmission.

Brilliant. Many thanks.

The server has a new method: disable_observe_handling. You can find the usage in test case spawn_server_disable_observe.