Repository: eclipse/leshan
Pull request #856 (2020-06-19 16:09:43)

Add support of additional attributes for Bootstrap
We already have a way to access to  additional attributes for REGISTER request.
(Additional attributes are attributes which are not defined in the specification. see https://github.com/eclipse/leshan/pull/66)

This PR aims to do the same thing but for BOOTSTRAP request.


Patch:
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java
index d03e556b..06939c31 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClient.java
@@ -36,6 +36,7 @@ import org.eclipse.leshan.client.californium.object.ObjectResource;
 import org.eclipse.leshan.client.californium.request.CaliforniumLwM2mRequestSender;
 import org.eclipse.leshan.client.engine.RegistrationEngine;
 import org.eclipse.leshan.client.engine.RegistrationEngineFactory;
+import org.eclipse.leshan.client.engine.RegistrationEngineFactory2;
 import org.eclipse.leshan.client.observer.LwM2mClientObserver;
 import org.eclipse.leshan.client.observer.LwM2mClientObserverDispatcher;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
@@ -73,6 +74,16 @@ public class LeshanClient implements LwM2mClient {
             EndpointFactory endpointFactory, RegistrationEngineFactory engineFactory,
             Map<String, String> additionalAttributes, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder,
             ScheduledExecutorService sharedExecutor) {
+        this(endpoint, localAddress, objectEnablers, coapConfig, dtlsConfigBuilder, endpointFactory, engineFactory,
+                additionalAttributes, null, encoder, decoder, sharedExecutor);
+    }
+
+    /** @since 1.1 */
+    public LeshanClient(String endpoint, InetSocketAddress localAddress,
+            List<? extends LwM2mObjectEnabler> objectEnablers, NetworkConfig coapConfig, Builder dtlsConfigBuilder,
+            EndpointFactory endpointFactory, RegistrationEngineFactory engineFactory,
+            Map<String, String> additionalAttributes, Map<String, String> bsAdditionalAttributes,
+            LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder, ScheduledExecutorService sharedExecutor) {
 
         Validate.notNull(endpoint);
         Validate.notEmpty(objectEnablers);
@@ -83,8 +94,14 @@ public class LeshanClient implements LwM2mClient {
         bootstrapHandler = createBoostrapHandler(objectTree);
         endpointsManager = createEndpointsManager(localAddress, coapConfig, dtlsConfigBuilder, endpointFactory);
         requestSender = createRequestSender(endpointsManager, sharedExecutor);
-        engine = engineFactory.createRegistratioEngine(endpoint, objectTree, endpointsManager, requestSender,
-                bootstrapHandler, observers, additionalAttributes, sharedExecutor);
+        if (engineFactory instanceof RegistrationEngineFactory2) {
+            engine = ((RegistrationEngineFactory2) engineFactory).createRegistratioEngine(endpoint, objectTree,
+                    endpointsManager, requestSender, bootstrapHandler, observers, additionalAttributes,
+                    bsAdditionalAttributes, sharedExecutor);
+        } else {
+            engine = engineFactory.createRegistratioEngine(endpoint, objectTree, endpointsManager, requestSender,
+                    bootstrapHandler, observers, additionalAttributes, sharedExecutor);
+        }
 
         coapServer = createCoapServer(coapConfig, sharedExecutor);
         coapServer.add(createBootstrapResource(engine, bootstrapHandler));
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java
index 01c40bdc..9c4e96bf 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/LeshanClientBuilder.java
@@ -46,6 +46,7 @@ import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder;
 import org.eclipse.leshan.core.node.codec.LwM2mNodeEncoder;
 import org.eclipse.leshan.core.request.BindingMode;
+import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.util.Validate;
 
 /**
@@ -70,6 +71,9 @@ public class LeshanClientBuilder {
 
     private ScheduledExecutorService executor;
 
+    /** @since 1.1 */
+    protected Map<String, String> bsAdditionalAttributes;
+
     /**
      * Creates a new instance for setting the configuration options for a {@link LeshanClient} instance.
      * 
@@ -190,6 +194,15 @@ public class LeshanClientBuilder {
         return this;
     }
 
+    /**
+     * Set the additionalAttributes for {@link BootstrapRequest}
+     * @since 1.1
+     */
+    public LeshanClientBuilder setBootstrapAdditionalAttributes(Map<String, String> additionalAttributes) {
+        this.bsAdditionalAttributes = additionalAttributes;
+        return this;
+    }
+
     /**
      * Set a shared executor. This executor will be used everywhere it is possible. This is generally used when you want
      * to limit the number of thread to use or if you want to simulate a lot of clients sharing the same thread pool.
@@ -328,6 +341,6 @@ public class LeshanClientBuilder {
             Map<String, String> additionalAttributes, LwM2mNodeEncoder encoder, LwM2mNodeDecoder decoder,
             ScheduledExecutorService sharedExecutor) {
         return new LeshanClient(endpoint, localAddress, objectEnablers, coapConfig, dtlsConfigBuilder, endpointFactory,
-                engineFactory, additionalAttributes, encoder, decoder, executor);
+                engineFactory, additionalAttributes, bsAdditionalAttributes, encoder, decoder, executor);
     }
 }
diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java
index 02db2436..c1108de5 100644
--- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java
+++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/request/CoapRequestBuilder.java
@@ -51,7 +51,14 @@ public class CoapRequestBuilder implements UplinkRequestVisitor {
         coapRequest = Request.newPost();
         buildRequestSettings();
         coapRequest.getOptions().addUriPath("bs");
-        coapRequest.getOptions().addUriQuery("ep=" + request.getEndpointName());
+
+        // @since 1.1
+        HashMap<String, String> attributes = new HashMap<>();
+        attributes.putAll(request.getAdditionalAttributes());
+        attributes.put("ep", request.getEndpointName());
+        for (Entry<String, String> attr : attributes.entrySet()) {
+            coapRequest.getOptions().addUriQuery(attr.getKey() + "=" + attr.getValue());
+        }
     }
 
     @Override
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
index 7f075ac1..e26b70a9 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
@@ -96,6 +96,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
     // device state
     private final String endpoint;
     private final Map<String, String> additionalAttributes;
+    private final Map<String, String> bsAdditionalAttributes; // @since 1.1
     private final Map<Integer /* objectId */, LwM2mObjectEnabler> objectEnablers;
     private final Map<String /* registrationId */, ServerIdentity> registeredServers;
     private final List<ServerIdentity> registeringServers;
@@ -121,12 +122,25 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
             Map<String, String> additionalAttributes, ScheduledExecutorService executor, long requestTimeoutInMs,
             long deregistrationTimeoutInMs, int bootstrapSessionTimeoutInSec, int retryWaitingTimeInMs,
             Integer communicationPeriodInMs, boolean reconnectOnUpdate, boolean resumeOnConnect) {
+        this(endpoint, objectTree, endpointsManager, requestSender, bootstrapState, observer, additionalAttributes,
+                null, executor, requestTimeoutInMs, deregistrationTimeoutInMs, bootstrapSessionTimeoutInSec,
+                retryWaitingTimeInMs, communicationPeriodInMs, reconnectOnUpdate, resumeOnConnect);
+    }
+
+    /** @since 1.1 */
+    public DefaultRegistrationEngine(String endpoint, LwM2mObjectTree objectTree, EndpointsManager endpointsManager,
+            LwM2mRequestSender requestSender, BootstrapHandler bootstrapState, LwM2mClientObserver observer,
+            Map<String, String> additionalAttributes, Map<String, String> bsAdditionalAttributes,
+            ScheduledExecutorService executor, long requestTimeoutInMs, long deregistrationTimeoutInMs,
+            int bootstrapSessionTimeoutInSec, int retryWaitingTimeInMs, Integer communicationPeriodInMs,
+            boolean reconnectOnUpdate, boolean resumeOnConnect) {
         this.endpoint = endpoint;
         this.objectEnablers = objectTree.getObjectEnablers();
         this.bootstrapHandler = bootstrapState;
         this.endpointsManager = endpointsManager;
         this.observer = observer;
         this.additionalAttributes = additionalAttributes;
+        this.bsAdditionalAttributes = bsAdditionalAttributes;
         this.registeredServers = new ConcurrentHashMap<>();
         this.registeringServers = new CopyOnWriteArrayList<>();
         this.currentBoostrapServer = new AtomicReference<>();
@@ -203,7 +217,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
             // Send bootstrap request
             BootstrapRequest request = null;
             try {
-                request = new BootstrapRequest(endpoint);
+                request = new BootstrapRequest(endpoint, bsAdditionalAttributes);
                 if (observer != null) {
                     observer.onBootstrapStarted(bootstrapServer, request);
                 }
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java
index 68379f58..1aba2e67 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngineFactory.java
@@ -23,13 +23,17 @@ import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
 import org.eclipse.leshan.client.observer.LwM2mClientObserver;
 import org.eclipse.leshan.client.request.LwM2mRequestSender;
 import org.eclipse.leshan.client.resource.LwM2mObjectTree;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * A default implementation of {@link RegistrationEngineFactory}.
+ * A default implementation of {@link RegistrationEngineFactory2}.
  * <p>
  * It create a {@link DefaultRegistrationEngine} which could be configured. Look at all setter available in this class.
  */
-public class DefaultRegistrationEngineFactory implements RegistrationEngineFactory {
+public class DefaultRegistrationEngineFactory implements RegistrationEngineFactory2 {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultRegistrationEngineFactory.class);
 
     private long requestTimeoutInMs = 2 * 60 * 1000l; // 2min in ms
     private long deregistrationTimeoutInMs = 1000; // 1s in ms
@@ -43,14 +47,31 @@ public class DefaultRegistrationEngineFactory implements RegistrationEngineFacto
     }
 
     @Override
+    @Deprecated
     public RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree,
             EndpointsManager endpointsManager, LwM2mRequestSender requestSender, BootstrapHandler bootstrapState,
             LwM2mClientObserver observer, Map<String, String> additionalAttributes,
             ScheduledExecutorService sharedExecutor) {
-        return new DefaultRegistrationEngine(endpoint, objectTree, endpointsManager, requestSender, bootstrapState,
-                observer, additionalAttributes, sharedExecutor, requestTimeoutInMs, deregistrationTimeoutInMs,
-                bootstrapSessionTimeoutInSec, retryWaitingTimeInMs, communicationPeriodInMs, reconnectOnUpdate,
-                resumeOnConnect);
+        return null;
+    }
+
+    @Override
+    public RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree,
+            EndpointsManager endpointsManager, LwM2mRequestSender requestSender, BootstrapHandler bootstrapState,
+            LwM2mClientObserver observer, Map<String, String> additionalAttributes,
+            Map<String, String> bsAdditionalAttributes, ScheduledExecutorService sharedExecutor) {
+
+        RegistrationEngine engine = createRegistratioEngine(endpoint, objectTree, endpointsManager, requestSender,
+                bootstrapState, observer, additionalAttributes, sharedExecutor);
+        if (engine != null) {
+            LOG.warn("It seems you override a deprecated createRegistrationEngine method, you should use the new one");
+            return engine;
+        } else {
+            return new DefaultRegistrationEngine(endpoint, objectTree, endpointsManager, requestSender, bootstrapState,
+                    observer, additionalAttributes, bsAdditionalAttributes, sharedExecutor, requestTimeoutInMs,
+                    deregistrationTimeoutInMs, bootstrapSessionTimeoutInSec, retryWaitingTimeInMs,
+                    communicationPeriodInMs, reconnectOnUpdate, resumeOnConnect);
+        }
     }
 
     /**
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java
index 1b798133..dc7a4268 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory.java
@@ -26,7 +26,8 @@ import org.eclipse.leshan.client.resource.LwM2mObjectTree;
 
 /**
  * A factory for {@link RegistrationEngine}
- *
+ * 
+ * Since 1.1 {@link RegistrationEngineFactory2} should be preferred.
  */
 public interface RegistrationEngineFactory {
 
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory2.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory2.java
new file mode 100644
index 00000000..51c156b7
--- /dev/null
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/RegistrationEngineFactory2.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v20.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.client.engine;
+
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.eclipse.leshan.client.EndpointsManager;
+import org.eclipse.leshan.client.bootstrap.BootstrapHandler;
+import org.eclipse.leshan.client.observer.LwM2mClientObserver;
+import org.eclipse.leshan.client.request.LwM2mRequestSender;
+import org.eclipse.leshan.client.resource.LwM2mObjectTree;
+
+/**
+ * A new version of {@link RegistrationEngineFactory}
+ *
+ * @since 1.1
+ */
+public interface RegistrationEngineFactory2 extends RegistrationEngineFactory {
+
+    @Override
+    @Deprecated
+    RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree,
+            EndpointsManager endpointsManager, LwM2mRequestSender requestSender, BootstrapHandler bootstrapState,
+            LwM2mClientObserver observer, Map<String, String> additionalAttributes,
+            ScheduledExecutorService sharedExecutor);
+
+    RegistrationEngine createRegistratioEngine(String endpoint, LwM2mObjectTree objectTree,
+            EndpointsManager endpointsManager, LwM2mRequestSender requestSender, BootstrapHandler bootstrapState,
+            LwM2mClientObserver observer, Map<String, String> additionalAttributes,
+            Map<String, String> bsAdditionalAttributes, ScheduledExecutorService sharedExecutor);
+}
diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java
index 50d993b0..a73df30c 100644
--- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java
+++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java
@@ -186,6 +186,11 @@ public class LeshanClientDemo {
         aa.desc("Use additional attributes at registration time, syntax is \n -aa attrName1=attrValue1 attrName2=\\\"attrValue2\\\" ...");
         aa.hasArgs();
         options.addOption(aa.build());
+        Builder bsaa = Option.builder("bsaa");
+        bsaa.desc(
+                "Use additional attributes at bootstrap time, syntax is \n -bsaa attrName1=attrValue1 attrName2=\\\"attrValue2\\\" ...");
+        bsaa.hasArgs();
+        options.addOption(bsaa.build());
         options.addOption("m", true, "A folder which contains object models in OMA DDF(.xml)format.");
         options.addOption("pos", true,
                 "Set the initial location (latitude, longitude) of the device to be reported by the Location object.\n Format: lat_float:long_float");
@@ -299,7 +304,7 @@ public class LeshanClientDemo {
             communicationPeriod = Integer.valueOf(cl.getOptionValue("cp")) * 1000;
         }
 
-        // Get additional attributes
+        // Get additional attributes for registration
         Map<String, String> additionalAttributes = null;
         if (cl.hasOption("aa")) {
             additionalAttributes = new HashMap<>();
@@ -326,6 +331,33 @@ public class LeshanClientDemo {
             }
         }
 
+        // Get additional attributes for bootstrap
+        Map<String, String> bsAdditionalAttributes = null;
+        if (cl.hasOption("bsaa")) {
+            bsAdditionalAttributes = new HashMap<>();
+            Pattern p1 = Pattern.compile("(.*)=\"(.*)\"");
+            Pattern p2 = Pattern.compile("(.*)=(.*)");
+            String[] values = cl.getOptionValues("bsaa");
+            for (String v : values) {
+                Matcher m = p1.matcher(v);
+                if (m.matches()) {
+                    String attrName = m.group(1);
+                    String attrValue = m.group(2);
+                    bsAdditionalAttributes.put(attrName, attrValue);
+                } else {
+                    m = p2.matcher(v);
+                    if (m.matches()) {
+                        String attrName = m.group(1);
+                        String attrValue = m.group(2);
+                        bsAdditionalAttributes.put(attrName, attrValue);
+                    } else {
+                        System.err.println(String.format("Invalid syntax for additional attributes : %s", v));
+                        return;
+                    }
+                }
+            }
+        }
+
         // Get server URI
         String serverURI;
         if (cl.hasOption("u")) {
@@ -426,10 +458,11 @@ public class LeshanClientDemo {
         String modelsFolderPath = cl.getOptionValue("m");
 
         try {
-            createAndStartClient(endpoint, localAddress, localPort, cl.hasOption("b"), additionalAttributes, lifetime,
-                    communicationPeriod, serverURI, pskIdentity, pskKey, clientPrivateKey, clientPublicKey,
-                    serverPublicKey, clientCertificate, serverCertificate, latitude, longitude, scaleFactor,
-                    cl.hasOption("ocf"), cl.hasOption("oc"), cl.hasOption("r"), cl.hasOption("f"), modelsFolderPath);
+            createAndStartClient(endpoint, localAddress, localPort, cl.hasOption("b"), additionalAttributes,
+                    bsAdditionalAttributes, lifetime, communicationPeriod, serverURI, pskIdentity, pskKey,
+                    clientPrivateKey, clientPublicKey, serverPublicKey, clientCertificate, serverCertificate, latitude,
+                    longitude, scaleFactor, cl.hasOption("ocf"), cl.hasOption("oc"), cl.hasOption("r"),
+                    cl.hasOption("f"), modelsFolderPath);
         } catch (Exception e) {
             System.err.println("Unable to create and start client ...");
             e.printStackTrace();
@@ -438,12 +471,12 @@ public class LeshanClientDemo {
     }
 
     public static void createAndStartClient(String endpoint, String localAddress, int localPort, boolean needBootstrap,
-            Map<String, String> additionalAttributes, int lifetime, Integer communicationPeriod, String serverURI,
-            byte[] pskIdentity, byte[] pskKey, PrivateKey clientPrivateKey, PublicKey clientPublicKey,
-            PublicKey serverPublicKey, X509Certificate clientCertificate, X509Certificate serverCertificate,
-            Float latitude, Float longitude, float scaleFactor, boolean supportOldFormat,
-            boolean supportDeprecatedCiphers, boolean reconnectOnUpdate, boolean forceFullhandshake,
-            String modelsFolderPath) throws CertificateEncodingException {
+            Map<String, String> additionalAttributes, Map<String, String> bsAdditionalAttributes, int lifetime,
+            Integer communicationPeriod, String serverURI, byte[] pskIdentity, byte[] pskKey,
+            PrivateKey clientPrivateKey, PublicKey clientPublicKey, PublicKey serverPublicKey,
+            X509Certificate clientCertificate, X509Certificate serverCertificate, Float latitude, Float longitude,
+            float scaleFactor, boolean supportOldFormat, boolean supportDeprecatedCiphers, boolean reconnectOnUpdate,
+            boolean forceFullhandshake, String modelsFolderPath) throws CertificateEncodingException {
 
         locationInstance = new MyLocation(latitude, longitude, scaleFactor);
 
@@ -596,6 +629,7 @@ public class LeshanClientDemo {
             builder.setEncoder(new DefaultLwM2mNodeEncoder(true));
         }
         builder.setAdditionalAttributes(additionalAttributes);
+        builder.setBootstrapAdditionalAttributes(bsAdditionalAttributes);
         final LeshanClient client = builder.build();
 
         client.getObjectTree().addListener(new ObjectsListenerAdapter() {
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java
index f6ad2d39..747ffbaa 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BootstrapRequest.java
@@ -15,6 +15,10 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.request;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.eclipse.leshan.core.request.exception.InvalidRequestException;
 import org.eclipse.leshan.core.response.BootstrapResponse;
 
@@ -24,18 +28,34 @@ import org.eclipse.leshan.core.response.BootstrapResponse;
 public class BootstrapRequest implements UplinkRequest<BootstrapResponse> {
 
     private final String endpointName;
+    private final Map<String, String> additionalAttributes;
 
     public BootstrapRequest(String endpointName) throws InvalidRequestException {
+        this(endpointName, null);
+    }
+
+    /** @since 1.1 */
+    public BootstrapRequest(String endpointName, Map<String, String> additionalAttributes)
+            throws InvalidRequestException {
         if (endpointName == null || endpointName.isEmpty())
             throw new InvalidRequestException("endpoint is mandatory");
 
         this.endpointName = endpointName;
+        if (additionalAttributes == null)
+            this.additionalAttributes = Collections.emptyMap();
+        else
+            this.additionalAttributes = Collections.unmodifiableMap(new HashMap<>(additionalAttributes));
     }
 
     public String getEndpointName() {
         return endpointName;
     }
 
+    /** @since 1.1 */
+    public Map<String, String> getAdditionalAttributes() {
+        return additionalAttributes;
+    }
+
     @Override
     public void accept(UplinkRequestVisitor visitor) {
         visitor.visit(this);
@@ -68,6 +88,7 @@ public class BootstrapRequest implements UplinkRequest<BootstrapResponse> {
 
     @Override
     public String toString() {
-        return String.format("BootstrapRequest [endpointName=%s]", endpointName);
+        return String.format("BootstrapRequest [endpointName=%s, additionalAttributes=%s]", endpointName,
+                additionalAttributes);
     }
 }
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java
index 252acbad..82229335 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/bootstrap/BootstrapResource.java
@@ -17,6 +17,9 @@ package org.eclipse.leshan.server.californium.bootstrap;
 
 import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toCoapResponseCode;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.eclipse.californium.core.CoapResource;
 import org.eclipse.californium.core.coap.CoAP.Type;
 import org.eclipse.californium.core.coap.Request;
@@ -59,10 +62,15 @@ public class BootstrapResource extends LwM2mCoapResource {
 
         // which endpoint?
         String endpoint = null;
+        Map<String, String> additionalParams = new HashMap<>();
         for (String param : request.getOptions().getUriQuery()) {
             if (param.startsWith(QUERY_PARAM_ENDPOINT)) {
                 endpoint = param.substring(QUERY_PARAM_ENDPOINT.length());
-                break;
+            } else {
+                String[] tokens = param.split("\\=");
+                if (tokens != null && tokens.length == 2) {
+                    additionalParams.put(tokens[0], tokens[1]);
+                }
             }
         }
 
@@ -71,7 +79,7 @@ public class BootstrapResource extends LwM2mCoapResource {
 
         // handle bootstrap request
         SendableResponse<BootstrapResponse> sendableResponse = bootstrapHandler.bootstrap(clientIdentity,
-                new BootstrapRequest(endpoint));
+                new BootstrapRequest(endpoint, additionalParams));
         BootstrapResponse response = sendableResponse.getResponse();
         if (response.isSuccess()) {
             exchange.respond(toCoapResponseCode(response.getCode()));
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java
index 2c064624..a02d7b1a 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager.java
@@ -25,6 +25,8 @@ import org.eclipse.leshan.core.response.LwM2mResponse;
  * This class is responsible to accept or refuse to start new {@link BootstrapSession}. The session also contain the
  * ContentFormat which will be used to send Bootstrap Write Request.
  * 
+ * Since 1.1, you should rather use a {@link BootstrapSessionManager2}
+ * 
  * @see DefaultBootstrapSessionManager
  * @see BootstrapSession
  */
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager2.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager2.java
new file mode 100644
index 00000000..e22d8ba0
--- /dev/null
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/BootstrapSessionManager2.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v20.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server.bootstrap;
+
+import org.eclipse.leshan.core.request.BootstrapRequest;
+import org.eclipse.leshan.core.request.Identity;
+
+/**
+ * A new version of {@link BootstrapSessionManager}.
+ * 
+ * @since 1.1
+ */
+public interface BootstrapSessionManager2 extends BootstrapSessionManager {
+
+    @Override
+    @Deprecated
+    BootstrapSession begin(String endpoint, Identity clientIdentity);
+
+    /**
+     * Starts a bootstrapping session for an endpoint. In particular, this is responsible for authorizing the endpoint
+     * if applicable.
+     * 
+     * @param request the bootstrap request which initiates the session.
+     * @param clientIdentity the {@link Identity} of the client.
+     * 
+     * @return a BootstrapSession, possibly authorized.
+     * 
+     * @since 1.1
+     */
+    public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity);
+}
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
index 5f1c1e24..c87f02f1 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapHandler.java
@@ -81,7 +81,13 @@ public class DefaultBootstrapHandler implements BootstrapHandler {
         String endpoint = request.getEndpointName();
 
         // Start session, checking the BS credentials
-        final BootstrapSession session = this.sessionManager.begin(endpoint, sender);
+        final BootstrapSession session;
+        // @since 1.1
+        if (sessionManager instanceof BootstrapSessionManager2) {
+            session = ((BootstrapSessionManager2) sessionManager).begin(request, sender);
+        } else {
+            session = this.sessionManager.begin(endpoint, sender);
+        }
 
         if (!session.isAuthorized()) {
             sessionManager.failed(session, UNAUTHORIZED);
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java
index 4b0cf02f..7ff48c58 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSession.java
@@ -15,9 +15,11 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.bootstrap;
 
+import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.util.RandomStringUtils;
+import org.eclipse.leshan.core.util.Validate;
 
 /**
  * A default implementation for {@link BootstrapSession}
@@ -30,6 +32,7 @@ public class DefaultBootstrapSession implements BootstrapSession {
     private final boolean authorized;
     private final ContentFormat contentFormat;
     private final long creationTime;
+    private final BootstrapRequest request;
 
     private volatile boolean cancelled = false;
 
@@ -40,11 +43,28 @@ public class DefaultBootstrapSession implements BootstrapSession {
      * @param endpoint The endpoint of the device.
      * @param identity The transport layer identity of the device.
      * @param authorized True if device is authorized to bootstrap.
+     * 
+     * @deprecated use {@link #DefaultBootstrapSession(BootstrapRequest, Identity, boolean)} instead
      */
+    @Deprecated
     public DefaultBootstrapSession(String endpoint, Identity identity, boolean authorized) {
         this(endpoint, identity, authorized, ContentFormat.TLV);
     }
 
+    /**
+     * Create a {@link DefaultBootstrapSession} using default {@link ContentFormat#TLV} content format and using
+     * <code>System.currentTimeMillis()</code> to set the creation time.
+     * 
+     * @param request The bootstrap request which initiate the session.
+     * @param identity The transport layer identity of the device.
+     * @param authorized True if device is authorized to bootstrap.
+     * 
+     * @since 1.1
+     */
+    public DefaultBootstrapSession(BootstrapRequest request, Identity identity, boolean authorized) {
+        this(request, identity, authorized, ContentFormat.TLV);
+    }
+
     /**
      * Create a {@link DefaultBootstrapSession} using <code>System.currentTimeMillis()</code> to set the creation time.
      * 
@@ -52,12 +72,30 @@ public class DefaultBootstrapSession implements BootstrapSession {
      * @param identity The transport layer identity of the device.
      * @param authorized True if device is authorized to bootstrap.
      * @param contentFormat The content format to use to write object.
+     * 
+     * @deprecated use {@link #DefaultBootstrapSession(BootstrapRequest, Identity, boolean, ContentFormat)} instead
      */
+    @Deprecated
     public DefaultBootstrapSession(String endpoint, Identity identity, boolean authorized,
             ContentFormat contentFormat) {
         this(endpoint, identity, authorized, contentFormat, System.currentTimeMillis());
     }
 
+    /**
+     * Create a {@link DefaultBootstrapSession} using <code>System.currentTimeMillis()</code> to set the creation time.
+     * 
+     * @param request The bootstrap request which initiate the session.
+     * @param identity The transport layer identity of the device.
+     * @param authorized True if device is authorized to bootstrap.
+     * @param contentFormat The content format to use to write object.
+     * 
+     * @since 1.1
+     */
+    public DefaultBootstrapSession(BootstrapRequest request, Identity identity, boolean authorized,
+            ContentFormat contentFormat) {
+        this(request, identity, authorized, contentFormat, System.currentTimeMillis());
+    }
+
     /**
      * Create a {@link DefaultBootstrapSession}.
      * 
@@ -66,11 +104,38 @@ public class DefaultBootstrapSession implements BootstrapSession {
      * @param authorized True if device is authorized to bootstrap.
      * @param contentFormat The content format to use to write object.
      * @param creationTime The creation time of this session in ms.
+     * 
+     * @deprecated use {@link #DefaultBootstrapSession(BootstrapRequest, Identity, boolean, ContentFormat, long)}
      */
+    @Deprecated
     public DefaultBootstrapSession(String endpoint, Identity identity, boolean authorized, ContentFormat contentFormat,
             long creationTime) {
         this.id = RandomStringUtils.random(10, true, true);
-        this.endpoint = endpoint;
+        this.request = null;
+        this.endpoint = request.getEndpointName();
+        this.identity = identity;
+        this.authorized = authorized;
+        this.contentFormat = contentFormat;
+        this.creationTime = creationTime;
+    }
+
+    /**
+     * Create a {@link DefaultBootstrapSession}.
+     * 
+     * @param request The bootstrap request which initiate the session.
+     * @param identity The transport layer identity of the device.
+     * @param authorized True if device is authorized to bootstrap.
+     * @param contentFormat The content format to use to write object.
+     * @param creationTime The creation time of this session in ms.
+     * 
+     * @since 1.1
+     */
+    public DefaultBootstrapSession(BootstrapRequest request, Identity identity, boolean authorized,
+            ContentFormat contentFormat, long creationTime) {
+        Validate.notNull(request);
+        this.id = RandomStringUtils.random(10, true, true);
+        this.request = request;
+        this.endpoint = request.getEndpointName();
         this.identity = identity;
         this.authorized = authorized;
         this.contentFormat = contentFormat;
@@ -107,6 +172,10 @@ public class DefaultBootstrapSession implements BootstrapSession {
         return creationTime;
     }
 
+    public BootstrapRequest getBootstrapRequest() {
+        return request;
+    }
+
     @Override
     public void cancel() {
         cancelled = true;
@@ -120,7 +189,7 @@ public class DefaultBootstrapSession implements BootstrapSession {
     @Override
     public String toString() {
         return String.format(
-                "BootstrapSession [id=%s, endpoint=%s, identity=%s, authorized=%s, contentFormat=%s, creationTime=%dms, cancelled=%s]",
-                id, endpoint, identity, authorized, contentFormat, creationTime, cancelled);
+                "DefaultBootstrapSession [id=%s, endpoint=%s, identity=%s, authorized=%s, contentFormat=%s, creationTime=%s, request=%s, cancelled=%s]",
+                id, endpoint, identity, authorized, contentFormat, creationTime, request, cancelled);
     }
 }
\ No newline at end of file
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java
index 6654ff91..3c5167c2 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/bootstrap/DefaultBootstrapSessionManager.java
@@ -18,6 +18,7 @@ package org.eclipse.leshan.server.bootstrap;
 import java.util.List;
 
 import org.eclipse.leshan.core.request.BootstrapFinishRequest;
+import org.eclipse.leshan.core.request.BootstrapRequest;
 import org.eclipse.leshan.core.request.DownlinkRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.response.LwM2mResponse;
@@ -34,7 +35,7 @@ import org.slf4j.LoggerFactory;
  * <p>
  * Nothing specific is done on session's end.
  */
-public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
+public class DefaultBootstrapSessionManager implements BootstrapSessionManager2 {
 
     private static final Logger LOG = LoggerFactory.getLogger(DefaultBootstrapSessionManager.class);
 
@@ -63,15 +64,27 @@ public class DefaultBootstrapSessionManager implements BootstrapSessionManager {
     }
 
     @Override
+    @Deprecated
     public BootstrapSession begin(String endpoint, Identity clientIdentity) {
-        boolean authorized;
-        if (bsSecurityStore != null) {
-            List<SecurityInfo> securityInfos = bsSecurityStore.getAllByEndpoint(endpoint);
-            authorized = securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos);
+        return null;
+    }
+
+    @Override
+    public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
+        BootstrapSession session = begin(request.getEndpointName(), clientIdentity);
+        if (session != null) {
+            LOG.warn("It seems you override a deprecated begin method, you should use the new one");
         } else {
-            authorized = true;
+            boolean authorized;
+            if (bsSecurityStore != null) {
+                List<SecurityInfo> securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
+                authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity,
+                        securityInfos);
+            } else {
+                authorized = true;
+            }
+            session = new DefaultBootstrapSession(request, clientIdentity, authorized);
         }
-        DefaultBootstrapSession session = new DefaultBootstrapSession(endpoint, clientIdentity, authorized);
         LOG.trace("Bootstrap session started : {}", session);
         return session;
     }


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java
index 4b52018e..f8e6b21a 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java
@@ -34,6 +34,7 @@ import java.security.spec.KeySpec;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.eclipse.leshan.client.californium.LeshanClientBuilder;
 import org.eclipse.leshan.client.object.Device;
@@ -50,6 +51,8 @@ import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerConfig;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfig.ServerSecurity;
 import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
 import org.eclipse.leshan.server.bootstrap.BootstrapSession;
+import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
+import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
 import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
 import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder;
 import org.eclipse.leshan.server.security.BootstrapSecurityStore;
@@ -65,6 +68,7 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
     public LeshanBootstrapServer bootstrapServer;
     public final PublicKey bootstrapServerPublicKey;
     public final PrivateKey bootstrapServerPrivateKey;
+    public volatile DefaultBootstrapSession lastBootstrapSession;
 
     public BootstrapIntegrationTestHelper() {
         super();
@@ -113,6 +117,12 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         builder.setLocalSecureAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         builder.setPrivateKey(bootstrapServerPrivateKey);
         builder.setPublicKey(bootstrapServerPublicKey);
+        builder.setSessionManager(new DefaultBootstrapSessionManager(securityStore) {
+            @Override
+            public void end(BootstrapSession bsSession) {
+                lastBootstrapSession = (DefaultBootstrapSession) bsSession;
+            }
+        });
 
         bootstrapServer = builder.build();
     }
@@ -154,6 +164,16 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
     }
 
     public void createClient(Security security, ObjectsInitializer initializer) {
+        createClient(security, initializer, null);
+    }
+
+    @Override
+    public void createClient(Map<String, String> additionalAttributes) {
+        createClient(withoutSecurity(), null, additionalAttributes);
+    }
+
+    public void createClient(Security security, ObjectsInitializer initializer,
+            Map<String, String> additionalAttributes) {
         if (initializer == null) {
             initializer = new ObjectsInitializer();
         }
@@ -163,13 +183,14 @@ public class BootstrapIntegrationTestHelper extends SecureIntegrationTestHelper
         initializer.setInstancesForObject(LwM2mId.DEVICE,
                 new Device("Eclipse Leshan", IntegrationTestHelper.MODEL_NUMBER, "12345", "U"));
         initializer.setClassForObject(LwM2mId.SERVER, DummyInstanceEnabler.class);
-        createClient(initializer);
+        createClient(initializer, additionalAttributes);
     }
 
-    public void createClient(ObjectsInitializer initializer) {
+    public void createClient(ObjectsInitializer initializer, Map<String, String> additionalAttributes) {
         // Create Leshan Client
         LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint());
         builder.setObjects(initializer.createAll());
+        builder.setBootstrapAdditionalAttributes(additionalAttributes);
         client = builder.build();
         setupClientMonitoring();
     }
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java
index ec628a00..38fa7206 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapTest.java
@@ -20,6 +20,9 @@ import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.*;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectsInitializer;
 import org.eclipse.leshan.client.resource.SimpleInstanceEnabler;
@@ -75,6 +78,35 @@ public class BootstrapTest {
         helper.assertClientRegisterered();
     }
 
+    @Test
+    public void bootstrapWithAdditionalAttributes() {
+        // Create DM Server without security & start it
+        helper.createServer();
+        helper.server.start();
+
+        // Create and start bootstrap server
+        helper.createBootstrapServer(null);
+        helper.bootstrapServer.start();
+
+        // Create Client with additional attributes
+        // and check it is not already registered
+        Map<String, String> additionalAttributes = new HashMap<>();
+        additionalAttributes.put("key1", "value1");
+        additionalAttributes.put("imei", "2136872368");
+        helper.createClient(additionalAttributes);
+        helper.assertClientNotRegisterered();
+
+        // Start it and wait for registration
+        helper.client.start();
+        helper.waitForRegistrationAtServerSide(1);
+
+        // check the client is registered
+        helper.assertClientRegisterered();
+
+        // assert session contains additional attributes
+        assertEquals(additionalAttributes, helper.lastBootstrapSession.getBootstrapRequest().getAdditionalAttributes());
+    }
+
     @Test
     public void bootstrapDeleteSecurity() {
         // Create DM Server without security & start it
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
index af41b7c3..3900494d 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/bootstrap/BootstrapHandlerTest.java
@@ -223,7 +223,7 @@ public class BootstrapHandlerTest {
 
         @Override
         public BootstrapSession begin(String endpoint, Identity clientIdentity) {
-            lastSession = new DefaultBootstrapSession(endpoint, clientIdentity, authorized);
+            lastSession = new DefaultBootstrapSession(new BootstrapRequest(endpoint), clientIdentity, authorized);
             return lastSession;
         }