@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m ;
import com.fasterxml.jackson.core.type.TypeReference ;
import lombok.extern.slf4j.Slf4j ;
import org.apache.commons.io.IOUtils ;
import org.eclipse.californium.elements.config.Configuration ;
import org.eclipse.leshan.client.object.Security ;
@ -35,12 +36,22 @@ import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKBootstrapClientCredential ;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential ;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration ;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData ;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration ;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration ;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration ;
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration ;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential ;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential ;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential ;
import org.thingsboard.server.common.data.query.EntityData ;
import org.thingsboard.server.common.data.query.EntityDataPageLink ;
import org.thingsboard.server.common.data.query.EntityDataQuery ;
@ -58,17 +69,157 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd ;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Base64 ;
import java.util.Collections ;
import java.util.HashSet ;
import java.util.List ;
import java.util.Set ;
import java.util.concurrent.Executors ;
import java.util.concurrent.ScheduledExecutorService ;
import java.util.concurrent.TimeUnit ;
import static org.awaitility.Awaitility.await ;
import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT ;
import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT ;
import static org.eclipse.leshan.client.object.Security.noSec ;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status ;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.NO_SEC ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH ;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE ;
@Slf4j
@DaoSqlTest
public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
// Lwm2m Server
public static final int port = 5685 ;
public static final int securityPort = 5686 ;
public static final int portBs = 5687 ;
public static final int securityPortBs = 5688 ;
public static final String host = "localhost" ;
public static final String hostBs = "localhost" ;
public static final int shortServerId = 123 ;
public static final int shortServerIdBs = 111 ;
public static final String COAP = "coap://" ;
public static final String COAPS = "coaps://" ;
public static final String URI = COAP + host + ":" + port ;
public static final String SECURE_URI = COAPS + host + ":" + securityPort ;
public static final Configuration COAP_CONFIG = new Configuration ( ) . set ( COAP_PORT , port ) . set ( COAP_SECURE_PORT , securityPort ) ;
public static final Security SECURITY_NO_SEC = noSec ( URI , shortServerId ) ;
public static final Configuration SECURE_COAP_CONFIG = COAP_CONFIG . set ( COAP_SECURE_PORT , securityPort ) ;
public static final Configuration COAP_CONFIG_BS = new Configuration ( ) . set ( COAP_PORT , portBs ) ;
public static final String URI_BS = COAP + hostBs + ":" + portBs ;
public static final String SECURE_URI_BS = COAPS + hostBs + ":" + securityPortBs ;
protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS =
" {\n" +
" \"keyName\": {},\n" +
" \"observe\": [],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [],\n" +
" \"attributeLwm2m\": {}\n" +
" }" ;
protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS =
" {\n" +
" \"keyName\": {\n" +
" \"/3_1.0/0/9\": \"batteryLevel\"\n" +
" },\n" +
" \"observe\": [],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"/3_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }" ;
protected final String CLIENT_LWM2M_SETTINGS =
" {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }" ;
protected final String TRANSPORT_CONFIGURATION_NO_SEC_WITHOUT_ATTRIBUTES = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {},\n" +
" \"observe\": [],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"" + hostBs + "\",\n" +
" \"port\": " + portBs + ",\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"" + NO_SEC . name ( ) + "\",\n" +
" \"shortServerId\": " + shortServerIdBs + ",\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" +
" \"host\": \"" + host + "\",\n" +
" \"port\": " + port + ",\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"" + NO_SEC . name ( ) + "\",\n" +
" \"shortServerId\": " + shortServerId + ",\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}" ;
protected final String TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
@ -86,12 +237,12 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0 \",\n" +
" \"port\": 5687 ,\n" +
" \"host\": \"" + hostBs + " \",\n" +
" \"port\": " + portBs + " ,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111 ,\n" +
" \"securityMode\": \"" + NO_SEC . name ( ) + " \",\n" +
" \"shortServerId\": " + shortServerIdBs + " ,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
@ -100,12 +251,12 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" +
" \"host\": \"0.0.0.0 \",\n" +
" \"port\": 5685 ,\n" +
" \"host\": \"" + host + " \",\n" +
" \"port\": " + port + " ,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123 ,\n" +
" \"securityMode\": \"" + NO_SEC . name ( ) + " \",\n" +
" \"shortServerId\": " + shortServerId + " ,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
@ -127,21 +278,20 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
" }\n" +
"}" ;
protected final Lwm2mDeviceProfileTransportConfiguration LWM2M_TRANSPORT_CONFIGURATION_NO_SEC_WITH_ATTRIBUTES = JacksonUtil . fromString ( TRANSPORT_CONFIGURATION , Lwm2mDeviceProfileTransportConfiguration . class ) ;
protected final Lwm2mDeviceProfileTransportConfiguration LWM2M_TRANSPORT_CONFIGURATION_NO_SEC_WITHOUT_ATTRIBUTES = JacksonUtil . fromString ( TRANSPORT_CONFIGURATION_NO_SEC_WITHOUT_ATTRIBUTES , Lwm2mDeviceProfileTransportConfiguration . class ) ;
protected final Set < Lwm2mTestHelper . LwM2MClientState > expectedStatusesRegistrationLwm2mSuccess = new HashSet < > ( Arrays . asList ( ON_INIT , ON_REGISTRATION_STARTED , ON_REGISTRATION_SUCCESS ) ) ;
protected final Set < Lwm2mTestHelper . LwM2MClientState > expectedStatusesRegistrationBsSuccess = new HashSet < > ( Arrays . asList ( ON_INIT , ON_BOOTSTRAP_STARTED , ON_BOOTSTRAP_SUCCESS , ON_REGISTRATION_STARTED , ON_REGISTRATION_SUCCESS ) ) ;
protected DeviceProfile deviceProfile ;
protected ScheduledExecutorService executor ;
protected TbTestWebSocketClient wsClient ;
protected LwM2MTestClient client ;
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials ;
private String [ ] resources ;
public AbstractLwM2MIntegrationTest ( ) {
this . defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials ( ) ;
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential ( ) ;
this . defaultBootstrapCredentials . setBootstrapServer ( serverCredentials ) ;
this . defaultBootstrapCredentials . setLwm2mServer ( serverCredentials ) ;
}
public void init ( ) throws Exception {
public void init ( ) throws Exception {
executor = Executors . newScheduledThreadPool ( 10 , ThingsBoardThreadFactory . forName ( "test-lwm2m-scheduled" ) ) ;
loginTenantAdmin ( ) ;
@ -167,17 +317,42 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
@After
public void after ( ) {
wsClient . close ( ) ;
clientDestroy ( ) ;
executor . shutdownNow ( ) ;
}
public void basicTestConnection ( Security security ,
LwM2MDeviceCredentials deviceCredentials ,
Configuration coapConfig ,
String endpoint ,
Lwm2mDeviceProfileTransportConfiguration transportConfiguration ,
String awaitAlias , Set < LwM2MClientState > expectedStatuses ,
boolean isBootstrap , LwM2MSecurityMode mode ) throws Exception {
createDeviceProfile ( transportConfiguration ) ;
createDevice ( deviceCredentials , endpoint ) ;
createNewClient ( security , coapConfig , false , endpoint , isBootstrap ) ;
await ( awaitAlias )
. atMost ( 1000 , TimeUnit . MILLISECONDS )
. until ( ( ) - > ON_REGISTRATION_SUCCESS . equals ( client . getClientState ( ) ) ) ;
Assert . assertEquals ( expectedStatuses , client . getClientStates ( ) ) ;
client . destroy ( ) ;
expectedStatuses . add ( ON_DEREGISTRATION_STARTED ) ;
expectedStatuses . add ( ON_DEREGISTRATION_SUCCESS ) ;
await ( awaitAlias )
. atMost ( 1000 , TimeUnit . MILLISECONDS )
. until ( ( ) - > ON_DEREGISTRATION_SUCCESS . equals ( client . getClientState ( ) ) ) ;
Assert . assertEquals ( expectedStatuses , client . getClientStates ( ) ) ;
}
public void basicTestConnectionObserveTelemetry ( Security security ,
LwM2MClientCredential credentials ,
LwM2MDeviceCredentials deviceC redentials ,
Configuration coapConfig ,
String endpoint ) throws Exception {
createDeviceProfile ( TRANSPORT_CONFIGURATION ) ;
Device device = createDevice ( credentials ) ;
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration ( OBSERVE_ATTRIBUTES_WITH_PARAMS , getBootstrapServerCredentialsNoSec ( NONE ) ) ;
createDeviceProfile ( transportConfiguration ) ;
Device device = createDevice ( deviceCredentials , endpoint ) ;
SingleEntityFilter sef = new SingleEntityFilter ( ) ;
sef . setSingleEntity ( device . getId ( ) ) ;
@ -194,7 +369,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient . waitForReply ( ) ;
wsClient . registerWaitForUpdate ( ) ;
createNewClient ( security , coapConfig , false , endpoint ) ;
createNewClient ( security , coapConfig , false , endpoint , false ) ;
String msg = wsClient . waitForUpdate ( ) ;
EntityDataUpdate update = mapper . readValue ( msg , EntityDataUpdate . class ) ;
@ -208,7 +383,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
Assert . assertEquals ( 42 , Long . parseLong ( tsValue . getValue ( ) ) ) ;
}
protected void createDeviceProfile ( String transportConfiguration ) throws Exception {
protected void createDeviceProfile ( Lwm2mDeviceProfileTransportConfiguration transportConfiguration ) throws Exception {
deviceProfile = new DeviceProfile ( ) ;
deviceProfile . setName ( "LwM2M" ) ;
deviceProfile . setType ( DeviceProfileType . DEFAULT ) ;
@ -220,16 +395,16 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
DeviceProfileData deviceProfileData = new DeviceProfileData ( ) ;
deviceProfileData . setConfiguration ( new DefaultDeviceProfileConfiguration ( ) ) ;
deviceProfileData . setProvisionConfiguration ( new DisabledDeviceProfileProvisionConfiguration ( null ) ) ;
deviceProfileData . setTransportConfiguration ( JacksonUtil . fromString ( transportConfiguration , Lwm2mDeviceProfileTransportConfiguration . class ) ) ;
deviceProfileData . setTransportConfiguration ( transportConfiguration ) ;
deviceProfile . setProfileData ( deviceProfileData ) ;
deviceProfile = doPost ( "/api/deviceProfile" , deviceProfile , DeviceProfile . class ) ;
Assert . assertNotNull ( deviceProfile ) ;
}
protected Device createDevice ( LwM2MClientCredential clientCredentials ) throws Exception {
protected Device createDevice ( LwM2MDeviceCredentials credentials , String endpoint ) throws Exception {
Device device = new Device ( ) ;
device . setName ( "Device A" ) ;
device . setName ( endpoint ) ;
device . setDeviceProfileId ( deviceProfile . getId ( ) ) ;
device . setTenantId ( tenantId ) ;
device = doPost ( "/api/device" , device , Device . class ) ;
@ -239,11 +414,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
doGet ( "/api/device/" + device . getId ( ) . getId ( ) . toString ( ) + "/credentials" , DeviceCredentials . class ) ;
Assert . assertEquals ( device . getId ( ) , deviceCredentials . getDeviceId ( ) ) ;
deviceCredentials . setCredentialsType ( DeviceCredentialsType . LWM2M_CREDENTIALS ) ;
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials ( ) ;
credentials . setClient ( clientCredentials ) ;
credentials . setBootstrap ( defaultBootstrapCredentials ) ;
// LwM2MDeviceCredentials credentials = getDeviceCredentials(clientCredentials, mode);
deviceCredentials . setCredentialsValue ( JacksonUtil . toString ( credentials ) ) ;
doPost ( "/api/device/credentials" , deviceCredentials ) . andExpect ( status ( ) . isOk ( ) ) ;
return device ;
@ -259,11 +430,11 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
this . resources = resources ;
}
public void createNewClient ( Security security , Configuration coapConfig , boolean isRpc , String endpoint ) throws Exception {
public void createNewClient ( Security security , Configuration coapConfig , boolean isRpc , String endpoint , boolean isBootstrap ) throws Exception {
clientDestroy ( ) ;
client = new LwM2MTestClient ( this . executor , endpoint ) ;
int clientPort = SocketUtils . findAvailableTcpPort ( ) ;
client . init ( security , coapConfig , clientPort , isRpc ) ;
client . init ( security , coapConfig , clientPort , isRpc , isBootstrap , this . shortServerId , this . shortServerIdBs ) ;
}
private void clientDestroy ( ) {
@ -271,4 +442,54 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
client . destroy ( ) ;
}
}
protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration ( String observeAttr , List < LwM2MBootstrapServerCredential > bootstrapServerCredentials ) {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration ( ) ;
TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil . fromString ( observeAttr , TelemetryMappingConfiguration . class ) ;
OtherConfiguration clientLwM2mSettings = JacksonUtil . fromString ( CLIENT_LWM2M_SETTINGS , OtherConfiguration . class ) ;
transportConfiguration . setBootstrapServerUpdateEnable ( true ) ;
transportConfiguration . setObserveAttr ( observeAttrConfiguration ) ;
transportConfiguration . setClientLwM2mSettings ( clientLwM2mSettings ) ;
transportConfiguration . setBootstrap ( bootstrapServerCredentials ) ;
return transportConfiguration ;
}
protected List < LwM2MBootstrapServerCredential > getBootstrapServerCredentialsNoSec ( LwM2MProfileBootstrapConfigType bootstrapConfigType ) {
List < LwM2MBootstrapServerCredential > bootstrap = new ArrayList < > ( ) ;
switch ( bootstrapConfigType ) {
case BOTH :
bootstrap . add ( getBootstrapServerCredentialNoSec ( false ) ) ;
bootstrap . add ( getBootstrapServerCredentialNoSec ( true ) ) ;
break ;
case BOOTSTRAP_ONLY :
bootstrap . add ( getBootstrapServerCredentialNoSec ( true ) ) ;
break ;
case LWM2M_ONLY :
bootstrap . add ( getBootstrapServerCredentialNoSec ( false ) ) ;
break ;
case NONE :
}
return bootstrap ;
}
private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec ( boolean isBootstrap ) {
AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential ( ) ;
bootstrapServerCredential . setServerPublicKey ( "" ) ;
bootstrapServerCredential . setShortServerId ( isBootstrap ? shortServerIdBs : shortServerId ) ;
bootstrapServerCredential . setBootstrapServerIs ( isBootstrap ) ;
bootstrapServerCredential . setHost ( isBootstrap ? hostBs : host ) ;
bootstrapServerCredential . setPort ( isBootstrap ? portBs : port ) ;
return bootstrapServerCredential ;
}
protected LwM2MDeviceCredentials getDeviceCredentialsNoSec ( LwM2MClientCredential clientCredentials ) {
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials ( ) ;
credentials . setClient ( clientCredentials ) ;
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials ( ) ;
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential ( ) ;
bootstrapCredentials . setBootstrapServer ( serverCredentials ) ;
bootstrapCredentials . setLwm2mServer ( serverCredentials ) ;
credentials . setBootstrap ( bootstrapCredentials ) ;
return credentials ;
}
}