java-coap icon indicating copy to clipboard operation
java-coap copied to clipboard

General discussion / feedback from Leshan Team

Open sbernard31 opened this issue 2 years ago • 49 comments

Summary :

Leshan is a java implementation of LWM2M and until now it was strictly based on californium. Recently we did massive changes to abstract transport Layer. (See : https://github.com/eclipse/leshan/issues/1025)

Now to test this abstraction we are playing with java-coap (See : https://github.com/eclipse/leshan/issues/1373)

I will use this general issue to ask questions and provides feedback about it.

Some questions :

  1. In californium, there is a endpoint concept. If I simplify the concept a lot this is the socket where message are received. In californium a coap server can have several endpoints. If I understand java-coap correctly, 1 server is only associated to 1 socket right ?

  2. is there a way to check if a message (request or response) is CON or not ? something like : if (!CON.equals(coapRequest.getType()))

  3. is there a way to set a message (request or response) as CON ? something like : coapRequest.setConfirmable(true);

  4. is there a way to execute code after the message( mainly needed for response) is sent ? Maybe a callback once the message is sent ?

  5. Patch, IPatch and Fetch seems not implemented ? correct ? is it planned to implement it ? (I think we mainly need Ipatch and fetch for lwm2m composite operation)

  6. If you are curious you can have a look at how we are using java-coap, See : https://github.com/eclipse/leshan/compare/5b0b66fa675da2ad9224d15e828036d0c06d0f40 E.g : I created a kind of ResourceService to be able to add some Resource (see RegistrationResource), I don't know if this is a good idea or if this could give you some hint about changing java-coap API ? :man_shrugging:

(more question is coming soon :grin: )

sbernard31 avatar Jan 12 '23 17:01 sbernard31

Some answers

  1. In java-coap, socket is abstracted with CoapTransport interface. If you need to use multiple sockets, then it can be done by creating CoapTransport adapter that will proxy to multiple, underlying instances. Out of curiosity, what is the use case when you want to receive from multiple sockets?
  2. At the moment there is no way to check it if incoming request or response was NON. If needed this info can be easily added to TransportContext (lets create separate ticket).
  3. By default all outgoing requests are CON, to change it to NON one need to set it in TransportContext, for example: https://github.com/open-coap/java-coap/blob/master/coap-core/src/test/java/com/mbed/coap/server/messaging/ExchangeFilterTest.java#L105
  4. Not sure if I get you point, but you could wrap CoapTransport class and overwrite sendPacke method, there you could add callback to CompletableFuture.
  5. Yes, it is planned.
  6. Thanks for link, I will have a look.

szysas avatar Jan 13 '23 07:01 szysas

thx :pray:

  1. I don't know if I really need to have several socket by coapserver :grimacing:. What I know for sure is that 1 LWM2M server should have several socket open with different protocol. n socket for coap, n socket for coaps, n socket for coap+tcp etc etc... This is just different way for a client to connect/communicate to a server. With Californium I create 1 coap-server with several endpoints. And as I have Californium design/API in mind, I tried to do the same with java-coap... But maybe I should just create several coapserver ?

  2. #29 I created the ticket but no urgency to work on it. Maybe better to first identify all problems :) (unless you're sure you want to add it to java-coap)

  3. I will look at this.

  4. I will try it.

  5. Good to know.

  6. :warning: this draft code not so clean. (do not hesitate to ask question if you see something not clear)

sbernard31 avatar Jan 13 '23 15:01 sbernard31

  1. I think the only way would be to create multiple CoapServers, or on runtime client can create the one that is needed.

szysas avatar Jan 16 '23 08:01 szysas

Some more questions about observe. I think this will be one of the big part. Considering observe, a LWM2M server acts as a CoAP client (it sends request and receive notifications)

o1. I find API to send the observe request but didn't find how to get notification (I guess I should use ObservationConsumer but I'm not sure how)

o2. Leshan server based on Californium is able to persists "Observation", so if the server reboots no need to resent a new Observe request to new handle notification. Do you think this is doable with java-coap ? (if yes, could you share some hint)

sbernard31 avatar Jan 17 '23 14:01 sbernard31

I added a TokenGenerator to my experimentation, for now I go with a "not so java-coap" spirit way. (See https://github.com/eclipse/leshan/commit/914f0d48c2072518a76a454fd0adda7c1d533457). I guess it's OK for our POC.

But just by curiosity, reading https://github.com/open-coap/java-coap/issues/24#issuecomment-1349583886, I understand there is maybe a more elegant way to do with filter ?

I try to investigate a bit but I'm not sure to find how I should add a filter to my client which I created like this CoapClient coapClient = CoapClientBuilder.clientFor(destination.getIdentity().getPeerAddress(), coapServer);

I guess that I should customize server.clientService() ? so maybe I need to create CoapClient without using Builder ? Or maybe there is a way to customize clientService when I create coapServer but looking at the builder I didn't find anything.

I see there is an MIDSupplier but not a Token one.

I also maybe find a typo in coap server should inboundService be called outboundService ?

sbernard31 avatar Jan 17 '23 17:01 sbernard31

o1. To receive notifications, you could something like this:

        CoapClient client = CoapClientBuilder
                .newBuilder(new InetSocketAddress("localhost", 5683))
                .build();


        Function<CoapResponse, Boolean> consumer = coapResponse -> {
            System.out.println(coapResponse);
            return true; // return false to terminate observation
        };
        // send request to observe resource, observations will be handled in consumer callback
        CompletableFuture<CoapResponse> resp3 = client.observe("/sensors/temperature", consumer);

Note that this sets separate callback per observation relation. I'm thinking to simplify it and set one observation consumer in CoapClientBuilder, what do you think?

o2. Currently that is not possible. How important is that feature for integration with Leshan?

szysas avatar Jan 18 '23 13:01 szysas

Related to TokenGenerator, yes, it would be more 'elegant' and testable to create a filter. However I noticed that there is a missing method in builder class to add addition filters. I will try to improve it.

szysas avatar Jan 18 '23 13:01 szysas

I also maybe find a type in coap server should inboundService be called outboundService ?

You're correct, thanks for pointing this out.

szysas avatar Jan 18 '23 13:01 szysas

o1. I will try to play with your example then I let you know if 1 observation consumer seems better to me.

o2. It seems that users like observe feature but me not that much :grin: ... I try to rather encourage to use LWM2M Send Opeation which is a most simple alternative. Why I don't like it ? because we faced lot of issues with observe until now. If you're curious you could have a look at : https://github.com/eclipse/leshan/wiki/LWM2M-Observe So don't worry, I will be very surprised if java-coap fully support our needs out of the box concerning observe.

Currently that is not possible.

Maybe, it makes sense that some implementation support to persist observe relation and some others don't ? :thinking: So we could have a coap(s) support based on californium with persisted observation and coap(s)/coap(s)-tcp support based on java-coap without it ? I need to check if I can modify Leshan in that way.

How important is that feature for integration with Leshan?

For some use case this could be very important. Without going too deep in the details. I guess when you create a LWM2M server based on Leshan and you manage lot of clients in production with lot of Observe Relation. When you reboot your server, this will be an issue to resend all observe requests. (For sure at Sierra, we need it but at least at short/mid term we will still use Californium and Scandium for coap/coaps in production)

sbernard31 avatar Jan 18 '23 14:01 sbernard31

I could not agree more with your finding about observe.

Related to storing observation relations, it comes down not only to store Token->UriPath but also IP address... which is very fragile device association because those will change (NAT). Anyway, at least for that reason we would need to have single a single observation consumer.

szysas avatar Jan 19 '23 13:01 szysas

Thats seems to confirm that implementers don't like so much "Observe" where users like it :sweat_smile:

Related to storing observation relations, it comes down not only to store Token->UriPath but also IP address...

Ideally even more than a IP address, we find that to really make it work, it's better to store a kind of peer identify which are not the same depending of transport used or/and use cases. (coap => peer address / dtls => could be a connection Id or a psk identity or a public key .... / coap+tcp => a tcp connection ?)

About o2) I just realize that I talk only about persisting observations but actually this is more than that... Most of people who use Leshan in a production server want to be able to launch it in a cluster and so observation relation must be shared between all instance of the cluster. Currently in Californium this is achieve with a ObservationStore interface.

Let me know, if this is something you would "like" we explore together (first just by talking about it) or if you are too traumatized by "observe" feature :grin:

sbernard31 avatar Jan 19 '23 14:01 sbernard31

In com.mbed.coap.packet.Code, should it be ?

     C201_CREATED(2, 01, 201),
-    C202_DELETED(2, 02, 200),
+    C202_DELETED(2, 02, 202),
-    C203_VALID(2, 03, 200),
+    C203_VALID(2, 03, 203),
-    C204_CHANGED(2, 04, 200),
+    C204_CHANGED(2, 04, 204),
-    C205_CONTENT(2, 05, 200),
+    C205_CONTENT(2, 05, 205),

sbernard31 avatar Jan 19 '23 16:01 sbernard31

No, third parameter is HTTP status code.

szysas avatar Jan 20 '23 13:01 szysas

No, third parameter is HTTP status code.

:thinking: So I probably totally missed the purpose of this. Could you give me some hints ? :pray:

I was thinking this was just a way to get the code as int but not as it is encoded in CoAP (only 1 byte)

sbernard31 avatar Jan 20 '23 13:01 sbernard31

Yes, it is used to translate (proxy) with HTTP

szysas avatar Jan 20 '23 14:01 szysas

I have a first working version of observe with java-coap at LWM2M server side. If you're curious, the first draft looks like this.

Without support of cluster, I don't need "single observation consumer" (more than that if you look at the code I think I could not implement if with 1 single observation consumer because too much information is missing in coapresponse)

sbernard31 avatar Jan 20 '23 17:01 sbernard31

Please create separate ticket about observation consumer and let's discuss more there. For start, what context information would you need?

szysas avatar Jan 23 '23 13:01 szysas

Just to be sure this ticket should be about :

I'm thinking to simplify it and set one observation consumer in CoapClientBuilder

and ? or ?

persist observation / observation in a cluster

sbernard31 avatar Jan 24 '23 08:01 sbernard31

About storing observation relations.

szysas avatar Jan 25 '23 14:01 szysas

Please create separate ticket about observation consumer and let's discuss more there. For start, what context information would you need?

This is done : https://github.com/open-coap/java-coap/issues/36

sbernard31 avatar Jan 26 '23 11:01 sbernard31

(After an huge refactoring in our integrations tests https://github.com/eclipse/leshan/pull/1425 :tired_face: which will allow to reuse most of our tests with java-coap, I'm back to work on this again)

I'm currently using coapRequest.options().getUriQueryMap(). It works well when all part of the query are key value (e.g. field1=value1&field2=value2&field3=value3) But doesn't work if some key has no value (e.g. field1=value1&field2&field3)

This is needed in LWM2M for Q option of Register operation. (see : LWM2M-v1.1.1@transport§Table: 6.4.3.-1 Operation to Method and URI Mapping (Registration Interface))

Just in case I double check if this is valid in CoAP, I think it is the RFC says :

The query serves to further parameterize the resource. It consists of a sequence of arguments separated by an ampersand character (U+0026 AMPERSAND "&"). An argument is often in the form of a "key=value" pair.

(source : https://datatracker.ietf.org/doc/html/rfc7252#section-6.1)

  1. For each Uri-Query Option in the request, append a single character U+003F QUESTION MARK (?) (first option) or U+0026 AMPERSAND (&) (subsequent options) followed by the option's value to |resource name|, after converting any character that is not either in the "unreserved" set, in the "sub-delims" set (except U+0026 AMPERSAND (&)), a U+003A COLON (:), a U+0040 COMMERCIAL AT (@), a U+002F SOLIDUS (/), or a U+003F QUESTION MARK (?) character to its percent-encoded form.

(source : https://datatracker.ietf.org/doc/html/rfc7252#section-6.5)

Let me know, if you want to adapt java-coap code and if I should create a dedicated issue. :slightly_smiling_face:

sbernard31 avatar Apr 13 '23 16:04 sbernard31

Sounds like a missing feature so if you could create a separate ticket for this. That should not be too difficult to support. Thanks.

szysas avatar Apr 14 '23 09:04 szysas

For some integrations tests, I need to configure CoAP Timeout. With californium the code looks like :

// configure retransmission, with this configuration a request without ACK should timeout in
// ~200*5ms
configuration.set(CoapConfig.ACK_TIMEOUT, 200, TimeUnit.MILLISECONDS) //
                                .set(CoapConfig.ACK_INIT_RANDOM, 1f) //
                                .set(CoapConfig.ACK_TIMEOUT_SCALE, 1f) //
                                .set(CoapConfig.MAX_RETRANSMIT, 4);

In java-coap, I guess this can be done like this :

CoapServer.builder().timeout(new CoapTimeout(ackTimeout, maxRetransmit));

But there is no way to change ACK_RANDOM_FACTOR which should be what replaces ACK_INIT_RANDOM and ACK_TIMEOUT_SCALE.

For now I fallback with CoapServer.builder().timeout(new SingleTimeout(1000)); in my tests.

If this is something you want to add, I can create a new issue.

sbernard31 avatar Apr 18 '23 13:04 sbernard31

Adding parameter for ACK_RANDOM_FACTOR should be trivial thing to do, so if it helps you, please create a separate ticket.

By the way, there was API change on how to set retransmission strategy (it was inspired by one of our discussion in this area). Now it is for example:

CoapServer.builder()
   .retransmission(RetransmissionBackOff.ofExponential(Duration.ofMillis(100), 4))

szysas avatar Apr 19 '23 11:04 szysas

Not really a feature request but just a user feedback about CoapRequest API.

CoapRequest can be modified using some "setter".

Some of this setters are grouped as // --- MODIFIERS --- other are grouped under // --- OPTIONS MODIFIERS ---

Modifiers does not modify the instance but create (clone) a new CoapRequest from the original with modified field and returns it.
Option Modifiers modify directly the instance and return it.
(not sure I'm so clear :sweat_smile:)

Maybe there are good reason to do that but from a user point of view, I think this is a bit missleading.

My first try with the API looks like this and it took me some time to find that some 'setter' returns new instances.

// Create CoAP request from LWM2M Read Request
coapRequest = CoapRequest.get(getAddress(), getURI(request.getPath()));
coapRequest.token(tokenGenerator.createToken());  // this is the missleading part. 
if (request.getContentFormat() != null)
    coapRequest.options().setAccept(request.getContentFormat().getCode());

sbernard31 avatar Apr 19 '23 14:04 sbernard31

I didn't find the way to get token on CoapResponse, is there an API for this ?

I also see there is a way to get PeerAddress on CoapRequest but not on CoapResponse ? (This could be needed for observation but this more or less related to https://github.com/open-coap/java-coap/issues/36)

sbernard31 avatar Apr 20 '23 12:04 sbernard31

When we handle a message (mainly a request) is there a way to know local address (CoapTransport.getLocalSocketAddress()) which received it ?

sbernard31 avatar Apr 20 '23 15:04 sbernard31

Not really a feature request but just a user feedback about CoapRequest API.

CoapRequest can be modified using some "setter".

Some of this setters are grouped as // --- MODIFIERS --- other are grouped under // --- OPTIONS MODIFIERS ---

Modifiers does not modify the instance but create (clone) a new CoapRequest from the original with modified field and returns it. Option Modifiers modify directly the instance and return it. (not sure I'm so clear 😅)

Maybe there are good reason to do that but from a user point of view, I think this is a bit missleading.

My first try with the API looks like this and it took me some time to find that some 'setter' returns new instances.

// Create CoAP request from LWM2M Read Request
coapRequest = CoapRequest.get(getAddress(), getURI(request.getPath()));
coapRequest.token(tokenGenerator.createToken());  // this is the missleading part. 
if (request.getContentFormat() != null)
    coapRequest.options().setAccept(request.getContentFormat().getCode());

I agree, it is misleading. The idea is to have immutable CoapRequest and CoapResponse, so that if it's modified down the pipeline, there is no surprises. Currently header options are not immutable so that is one problem.

Maybe one solution would be to have dedicated builder class for CoapRequest and CoapResponse.

szysas avatar Apr 21 '23 12:04 szysas

I didn't find the way to get token on CoapResponse, is there an API for this ?

I also see there is a way to get PeerAddress on CoapRequest but not on CoapResponse ? (This could be needed for observation but this more or less related to #36)

Yes, there is no API to get token from coap-response. Observation are wrapped around SeparateResponse class will provide those metadata.

szysas avatar Apr 21 '23 12:04 szysas

When we handle a message (mainly a request) is there a way to know local address (CoapTransport.getLocalSocketAddress()) which received it ?

No, there is no such an API. What's the use case behind it?

szysas avatar Apr 21 '23 12:04 szysas