Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/firmware-checksum-autogenerating

pull/4503/head
zbeacon 6 years ago
parent
commit
84944f1f16
  1. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 47
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  3. 4
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  4. 113
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java
  5. 61
      application/src/main/resources/thingsboard.yml
  6. 58
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java
  7. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java
  8. 33
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  9. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/client/LwM2mSoftwareUpdate.java
  10. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java
  11. 47
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java
  12. 162
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  13. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  14. 168
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java
  15. 152
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  16. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java
  17. 111
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java
  18. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  19. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  20. 32
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java
  21. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java
  22. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java
  23. 108
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  24. 85
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  25. 31
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java
  26. 16
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  27. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  28. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  29. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java
  30. 0
      common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
  31. 0
      common/transport/lwm2m/src/main/resources/lwm2mserver.jks
  32. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java
  33. 217
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java
  34. 0
      transport/lwm2m/src/main/data/lwm2mserver.jks
  35. 70
      ui-ngx/src/app/core/services/mobile.service.ts
  36. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  37. 9
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss
  38. 23
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  39. 36
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss
  40. 5
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts
  41. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts
  42. 3
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  43. 100
      ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.html
  44. 253
      ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.ts
  45. 317
      ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.models.ts
  46. 5
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
  47. 30
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
  48. 182
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  49. 5
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  50. 97
      ui-ngx/src/app/shared/models/widget.models.ts
  51. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -132,7 +132,7 @@ import org.thingsboard.server.service.firmware.FirmwareStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeGrpcService; import org.thingsboard.server.service.edge.rpc.EdgeGrpcService;
import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.resource.TbResourceService;
@ -267,7 +267,7 @@ public abstract class BaseController {
protected TbDeviceProfileCache deviceProfileCache; protected TbDeviceProfileCache deviceProfileCache;
@Autowired @Autowired
protected LwM2MModelsRepository lwM2MModelsRepository; protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository;
@Autowired(required = false) @Autowired(required = false)
protected EdgeService edgeService; protected EdgeService edgeService;

47
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -533,6 +533,37 @@ public class DashboardController extends BaseController {
} }
} }
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody
public HomeDashboardInfo getHomeDashboardInfo() throws ThingsboardException {
try {
SecurityUser securityUser = getCurrentUser();
if (securityUser.isSystemAdmin()) {
return null;
}
User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId());
JsonNode additionalInfo = user.getAdditionalInfo();
HomeDashboardInfo homeDashboardInfo;
homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo);
if (homeDashboardInfo == null) {
if (securityUser.isCustomerUser()) {
Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId());
additionalInfo = customer.getAdditionalInfo();
homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo);
}
if (homeDashboardInfo == null) {
Tenant tenant = tenantService.findTenantById(securityUser.getTenantId());
additionalInfo = tenant.getAdditionalInfo();
homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo);
}
}
return homeDashboardInfo;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -582,6 +613,22 @@ public class DashboardController extends BaseController {
} }
} }
private HomeDashboardInfo extractHomeDashboardInfoFromAdditionalInfo(JsonNode additionalInfo) {
try {
if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) {
String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText();
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardId(dashboardId, Operation.READ);
boolean hideDashboardToolbar = true;
if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) {
hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean();
}
return new HomeDashboardInfo(dashboardId, hideDashboardToolbar);
}
} catch (Exception e) {}
return null;
}
private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) { private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) {
try { try {
if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) {

4
application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -47,9 +47,9 @@ public class Lwm2mController extends BaseController {
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode, public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode,
@PathVariable("bootstrapServerIs") boolean bootstrapServerIs) throws ThingsboardException { @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException {
try { try {
return lwM2MModelsRepository.getBootstrapSecurityInfo(securityMode, bootstrapServerIs); return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

113
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java → application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java

@ -16,14 +16,15 @@
package org.thingsboard.server.service.lwm2m; package org.thingsboard.server.service.lwm2m;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import java.math.BigInteger; import java.math.BigInteger;
@ -42,96 +43,59 @@ import java.security.spec.KeySpec;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MModelsRepository { public class LwM2MServerSecurityInfoRepository {
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private final LwM2MTransportServerConfig serverConfig;
private final LwM2MTransportBootstrapConfig bootstrapConfig;
@Autowired
LwM2MTransportConfigServer contextServer;
@Autowired
LwM2MTransportConfigBootstrap contextBootStrap;
/** /**
* @param securityMode * @param securityMode
* @param bootstrapServerIs * @param bootstrapServer
* @return ServerSecurityConfig more value is default: Important - port, host, publicKey * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
*/ */
public ServerSecurityConfig getBootstrapSecurityInfo(String securityMode, boolean bootstrapServerIs) { public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) {
LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase()); LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase());
return getBootstrapServer(bootstrapServerIs, lwM2MSecurityMode); ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode);
result.setBootstrapServerIs(bootstrapServer);
return result;
} }
/** private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) {
* @param bootstrapServerIs
* @param mode
* @return ServerSecurityConfig more value is default: Important - port, host, publicKey
*/
private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) {
ServerSecurityConfig bsServ = new ServerSecurityConfig(); ServerSecurityConfig bsServ = new ServerSecurityConfig();
bsServ.setBootstrapServerIs(bootstrapServerIs); bsServ.setServerId(serverConfig.getId());
if (bootstrapServerIs) { switch (mode) {
bsServ.setServerId(contextBootStrap.getBootstrapServerId()); case NO_SEC:
switch (mode) { bsServ.setHost(serverConfig.getHost());
case NO_SEC: bsServ.setPort(serverConfig.getPort());
bsServ.setHost(contextBootStrap.getBootstrapHost()); bsServ.setServerPublicKey("");
bsServ.setPort(contextBootStrap.getBootstrapPortNoSec()); break;
bsServ.setServerPublicKey(""); case PSK:
break; bsServ.setHost(serverConfig.getSecureHost());
case PSK: bsServ.setPort(serverConfig.getSecurePort());
bsServ.setHost(contextBootStrap.getBootstrapHostSecurity()); bsServ.setServerPublicKey("");
bsServ.setPort(contextBootStrap.getBootstrapPortSecurity()); break;
bsServ.setServerPublicKey(""); case RPK:
break; case X509:
case RPK: bsServ.setHost(serverConfig.getSecureHost());
case X509: bsServ.setPort(serverConfig.getSecurePort());
bsServ.setHost(contextBootStrap.getBootstrapHostSecurity()); bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY()));
bsServ.setPort(contextBootStrap.getBootstrapPortSecurity()); break;
bsServ.setServerPublicKey(getPublicKey (contextBootStrap.getBootstrapAlias(), this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY())); default:
break; break;
default:
break;
}
} else {
bsServ.setServerId(contextServer.getServerId());
switch (mode) {
case NO_SEC:
bsServ.setHost(contextServer.getServerHost());
bsServ.setPort(contextServer.getServerPortNoSec());
bsServ.setServerPublicKey("");
break;
case PSK:
bsServ.setHost(contextServer.getServerHostSecurity());
bsServ.setPort(contextServer.getServerPortSecurity());
bsServ.setServerPublicKey("");
break;
case RPK:
case X509:
bsServ.setHost(contextServer.getServerHostSecurity());
bsServ.setPort(contextServer.getServerPortSecurity());
bsServ.setServerPublicKey(getPublicKey (contextServer.getServerAlias(), this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY()));
break;
default:
break;
}
} }
return bsServ; return bsServ;
} }
private String getPublicKey (String alias, String publicServerX, String publicServerY) { private String getPublicKey(String alias, String publicServerX, String publicServerY) {
String publicKey = getServerPublicKeyX509(alias); String publicKey = getServerPublicKeyX509(alias);
return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY); return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY);
} }
/**
* @param alias
* @return PublicKey format HexString or null
*/
private String getServerPublicKeyX509(String alias) { private String getServerPublicKeyX509(String alias) {
try { try {
X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias); X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(alias);
return Hex.encodeHexString(serverCertificate.getEncoded()); return Hex.encodeHexString(serverCertificate.getEncoded());
} catch (CertificateEncodingException | KeyStoreException e) { } catch (CertificateEncodingException | KeyStoreException e) {
e.printStackTrace(); e.printStackTrace();
@ -139,11 +103,6 @@ public class LwM2MModelsRepository {
return null; return null;
} }
/**
* @param publicServerX
* @param publicServerY
* @return PublicKey format HexString or null
*/
private String getRPKPublicKey(String publicServerX, String publicServerY) { private String getRPKPublicKey(String publicServerX, String publicServerY) {
try { try {
/** Get Elliptic Curve Parameter spec for secp256r1 */ /** Get Elliptic Curve Parameter spec for secp256r1 */

61
application/src/main/resources/thingsboard.yml

@ -630,38 +630,14 @@ transport:
lwm2m: lwm2m:
# Enable/disable lvm2m transport protocol. # Enable/disable lvm2m transport protocol.
enabled: "${LWM2M_ENABLED:true}" enabled: "${LWM2M_ENABLED:true}"
# We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to
# send a Confirmable message to the time when an acknowledgement is no longer expected.
# DEFAULT_TIMEOUT = 2 * 60 * 1000l; 2 min in ms
timeout: "${LWM2M_TIMEOUT:120000}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
secure:
# Certificate_x509:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
# Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks"
key_store_path_file: "${KEY_STORE_PATH_FILE:}"
key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}"
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}"
server: server:
id: "${LWM2M_SERVER_ID:123}" id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC:5685}" bind_port: "${LWM2M_BIND_PORT:5685}"
secure: security:
bind_address_security: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY:5686}" bind_port: "${LWM2M_BIND_PORT_SECURITY:5686}"
# create_rpk: "${CREATE_RPK:}"
# Only for RPK: Public & Private Key. If the keystore file is missing or not working # Only for RPK: Public & Private Key. If the keystore file is missing or not working
# - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d0301070342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b]
# - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b],
# - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}"
public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
@ -671,19 +647,34 @@ transport:
enable: "${LWM2M_ENABLED_BS:true}" enable: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}" id: "${LWM2M_SERVER_ID_BS:111}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}" bind_port: "${LWM2M_BIND_PORT_BS:5687}"
secure: security:
bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
# Only for RPK: Public & Private Key. If the keystore file is missing or not working # Only for RPK: Public & Private Key. If the keystore file is missing or not working
# - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
# - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d030107034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34]
# - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34],
public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}"
public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
# Only Certificate_x509: # Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}" alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}"
security:
# Certificate_x509:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
# Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks"
key_store: "${LWM2M_KEY_STORE:lwm2mserver.jks}"
key_store_password: "${LWM2M_KEY_STORE_PASSWORD:server_ks_password}"
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}"
timeout: "${LWM2M_TIMEOUT:120000}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}"
# Use redis for Security and Registration stores # Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}" redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
snmp: snmp:

58
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java

@ -25,10 +25,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
@ -66,10 +69,10 @@ public class LwM2MTransportBootstrapServerConfiguration {
private boolean pskMode = false; private boolean pskMode = false;
@Autowired @Autowired
private LwM2MTransportContextBootstrap contextBs; private LwM2MTransportServerConfig serverConfig;
@Autowired @Autowired
private LwM2mTransportContextServer contextS; private LwM2MTransportContextBootstrap contextBs;
@Autowired @Autowired
private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
@ -81,13 +84,13 @@ public class LwM2MTransportBootstrapServerConfiguration {
@Bean @Bean
public LeshanBootstrapServer getLeshanBootstrapServer() { public LeshanBootstrapServer getLeshanBootstrapServer() {
log.info("Prepare and start BootstrapServer... PostConstruct"); log.info("Prepare and start BootstrapServer... PostConstruct");
return this.getLhBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(), this.contextBs.getCtxBootStrap().getBootstrapPortSecurity()); return this.getLhBootstrapServer(this.contextBs.getCtxBootStrap().getPort(), this.contextBs.getCtxBootStrap().getSecurePort());
} }
public LeshanBootstrapServer getLhBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort) { public LeshanBootstrapServer getLhBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort) {
LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPortNoSec); builder.setLocalAddress(this.contextBs.getCtxBootStrap().getHost(), bootstrapPortNoSec);
builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapHostSecurity(), bootstrapSecurePort); builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getSecureHost(), bootstrapSecurePort);
/** Create CoAP Config */ /** Create CoAP Config */
builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort)); builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort));
@ -106,14 +109,13 @@ public class LwM2MTransportBootstrapServerConfiguration {
/** Create and Set DTLS Config */ /** Create and Set DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getLwM2MTransportConfigServer().isRecommendedSupportedGroups()); dtlsConfig.setRecommendedSupportedGroupsOnly(serverConfig.isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getLwM2MTransportConfigServer().isRecommendedCiphers()); dtlsConfig.setRecommendedCipherSuitesOnly(serverConfig.isRecommendedCiphers());
if (this.pskMode) { if (this.pskMode) {
dtlsConfig.setSupportedCipherSuites( dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256); TLS_PSK_WITH_AES_128_CBC_SHA256);
} } else {
else {
dtlsConfig.setSupportedCipherSuites( dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_PSK_WITH_AES_128_CBC_SHA256,
@ -133,10 +135,10 @@ public class LwM2MTransportBootstrapServerConfiguration {
private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) {
try { try {
if (this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue() != null) { if (serverConfig.getKeyStoreValue() != null) {
KeyStore keyStoreServer = this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue(); KeyStore keyStoreServer = serverConfig.getKeyStoreValue();
if (this.setBuilderX509(builder)) { if (this.setBuilderX509(builder)) {
X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getLwM2MTransportConfigServer().getRootAlias()); X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(serverConfig.getRootCertificateAlias());
if (rootCAX509Cert != null) { if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1]; X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert; trustedCertificates[0] = rootCAX509Cert;
@ -167,12 +169,10 @@ public class LwM2MTransportBootstrapServerConfiguration {
* For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
*/ */
try { try {
X509Certificate serverCertificate = (X509Certificate) this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getCertificateAlias());
PrivateKey privateKey = (PrivateKey) this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getLwM2MTransportConfigServer().getKeyStorePasswordServer() == null ? null : this.contextS.getLwM2MTransportConfigServer().getKeyStorePasswordServer().toCharArray()); PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getCertificateAlias(), serverConfig.getKeyStorePassword() == null ? null : serverConfig.getKeyStorePassword().toCharArray());
PublicKey publicKey = serverCertificate.getPublicKey(); PublicKey publicKey = serverCertificate.getPublicKey();
if (serverCertificate != null && if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) {
privateKey != null && privateKey.getEncoded().length > 0 &&
publicKey != null && publicKey.getEncoded().length > 0) {
builder.setPublicKey(serverCertificate.getPublicKey()); builder.setPublicKey(serverCertificate.getPublicKey());
builder.setPrivateKey(privateKey); builder.setPrivateKey(privateKey);
builder.setCertificateChain(new X509Certificate[]{serverCertificate}); builder.setCertificateChain(new X509Certificate[]{serverCertificate});
@ -199,10 +199,12 @@ public class LwM2MTransportBootstrapServerConfiguration {
} }
private void infoPramsUri(String mode) { private void infoPramsUri(String mode) {
log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]", log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]",
mode, mode,
this.contextBs.getCtxBootStrap().getBootstrapHost() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(), this.contextBs.getCtxBootStrap().getHost(),
this.contextBs.getCtxBootStrap().getBootstrapHostSecurity() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortSecurity()); this.contextBs.getCtxBootStrap().getPort(),
this.contextBs.getCtxBootStrap().getSecureHost(),
this.contextBs.getCtxBootStrap().getSecurePort());
} }
@ -236,23 +238,25 @@ public class LwM2MTransportBootstrapServerConfiguration {
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1")); algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { LwM2MTransportBootstrapConfig serverConfig = this.contextBs.getCtxBootStrap();
if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) {
/** Get point values */ /** Get point values */
byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray());
/** Create key specs */ /** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec); parameterSpec);
/** Get public key */ /** Get public key */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
} }
if (this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().isEmpty()) { String privateEncodedKey = serverConfig.getPrivateEncoded();
if (StringUtils.isNotEmpty(privateEncodedKey)) {
/** Get private key */ /** Get private key */
byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().toCharArray()); byte[] privateS = Hex.decodeHex(privateEncodedKey.toCharArray());
try { try {
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS)); this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS));
} catch (InvalidKeySpecException ignore2) { } catch (InvalidKeySpecException ignore2) {
log.error("Invalid Bootstrap Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded()); log.error("Invalid Bootstrap Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", privateEncodedKey);
} }
} }
} }

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java

@ -34,7 +34,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
@Slf4j @Slf4j
@ -42,13 +42,13 @@ import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstr
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith'") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith'")
public class LwM2MTransportContextBootstrap extends TransportContext { public class LwM2MTransportContextBootstrap extends TransportContext {
private final LwM2MTransportConfigBootstrap lwM2MTransportConfigBootstrap; private final LwM2MTransportBootstrapConfig lwM2MTransportBootstrapConfig;
public LwM2MTransportContextBootstrap(LwM2MTransportConfigBootstrap ctxBootStrap) { public LwM2MTransportContextBootstrap(LwM2MTransportBootstrapConfig ctxBootStrap) {
this.lwM2MTransportConfigBootstrap = ctxBootStrap; this.lwM2MTransportBootstrapConfig = ctxBootStrap;
} }
public LwM2MTransportConfigBootstrap getCtxBootStrap() { public LwM2MTransportBootstrapConfig getCtxBootStrap() {
return this.lwM2MTransportConfigBootstrap; return this.lwM2MTransportBootstrapConfig;
} }
} }

33
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java

@ -34,8 +34,9 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -43,12 +44,12 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.BOOTSTRAP_SERVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.BOOTSTRAP_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_SERVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVERS; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVERS;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getBootstrapParametersFromThingsboard; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getBootstrapParametersFromThingsboard;
@Slf4j @Slf4j
@Service("LwM2MBootstrapSecurityStore") @Service("LwM2MBootstrapSecurityStore")
@ -59,17 +60,19 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
private final LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; private final LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator;
private final LwM2mTransportContextServer context; private final LwM2mTransportContext context;
private final LwM2mTransportServerHelper helper;
public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContextServer context) { public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) {
this.bootstrapConfigStore = bootstrapConfigStore; this.bootstrapConfigStore = bootstrapConfigStore;
this.lwM2MCredentialsSecurityInfoValidator = lwM2MCredentialsSecurityInfoValidator; this.lwM2MCredentialsSecurityInfoValidator = lwM2MCredentialsSecurityInfoValidator;
this.context = context; this.context = context;
this.helper = helper;
} }
@Override @Override
public List<SecurityInfo> getAllByEndpoint(String endPoint) { public List<SecurityInfo> getAllByEndpoint(String endPoint) {
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP); ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
/** add value to store from BootstrapJson */ /** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store); this.setBootstrapConfigScurityInfo(store);
@ -93,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override @Override
public SecurityInfo getByIdentity(String identity) { public SecurityInfo getByIdentity(String identity) {
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP); ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
/** add value to store from BootstrapJson */ /** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store); this.setBootstrapConfigScurityInfo(store);
@ -158,19 +161,19 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class);
LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class);
UUID sessionUUiD = UUID.randomUUID(); UUID sessionUUiD = UUID.randomUUID();
TransportProtos.SessionInfoProto sessionInfo = context.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits());
context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo)); context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo));
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint()); String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint());
context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo); helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
return lwM2MBootstrapConfig; return lwM2MBootstrapConfig;
} else { } else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo); helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
return null; return null;
} }
} }

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/client/LwM2mSoftwareUpdate.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.Data;
import java.util.UUID;
@Data
public class LwM2mSoftwareUpdate {
private volatile String clientSwVersion;
private volatile String currentSwVersion;
private volatile UUID currentSwId;
}

38
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.config;
public interface LwM2MSecureServerConfig {
Integer getId();
String getHost();
Integer getPort();
String getSecureHost();
Integer getSecurePort();
String getPublicX();
String getPublicY();
String getPrivateEncoded();
String getCertificateAlias();
}

47
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.transport.lwm2m; package org.thingsboard.server.transport.lwm2m.config;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -29,53 +29,42 @@ import java.util.Map;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportConfigBootstrap { public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig {
@Getter
@Value("${transport.lwm2m.bootstrap.enable:}")
private Boolean bootstrapEnable;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.id:}") @Value("${transport.lwm2m.bootstrap.id:}")
private Integer bootstrapServerId; private Integer id;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.bind_address:}") @Value("${transport.lwm2m.bootstrap.bind_address:}")
private String bootstrapHost; private String host;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.bind_port_no_sec:}") @Value("${transport.lwm2m.bootstrap.bind_port:}")
private Integer bootstrapPortNoSec; private Integer port;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_address_security:}") @Value("${transport.lwm2m.bootstrap.security.bind_address:}")
private String bootstrapHostSecurity; private String secureHost;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port_security:}") @Value("${transport.lwm2m.bootstrap.security.bind_port:}")
private Integer bootstrapPortSecurity; private Integer securePort;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.public_x:}") @Value("${transport.lwm2m.bootstrap.security.public_x:}")
private String bootstrapPublicX; private String publicX;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.public_y:}") @Value("${transport.lwm2m.bootstrap.security.public_y:}")
private String bootstrapPublicY; private String publicY;
@Getter @Getter
@Setter @Value("${transport.lwm2m.bootstrap.security.private_encoded:}")
private PublicKey bootstrapPublicKey; private String privateEncoded;
@Getter @Getter
@Value("${transport.lwm2m.bootstrap.secure.private_encoded:}") @Value("${transport.lwm2m.bootstrap.security.alias:}")
private String bootstrapPrivateEncoded; private String certificateAlias;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.alias:}")
private String bootstrapAlias;
@Getter
@Setter
private Map<String /** clientEndPoint */, TransportProtos.ValidateDeviceCredentialsResponseMsg> sessions;
} }

162
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.config;
import com.google.common.io.Resources;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Getter
@Setter
private LwM2mModelProvider modelProvider;
@Getter
@Value("${transport.lwm2m.timeout:}")
private Long timeout;
@Getter
@Value("${transport.sessions.report_timeout}")
private long sessionReportTimeout;
@Getter
@Value("${transport.lwm2m.recommended_ciphers:}")
private boolean recommendedCiphers;
@Getter
@Value("${transport.lwm2m.recommended_supported_groups:}")
private boolean recommendedSupportedGroups;
@Getter
@Value("${transport.lwm2m.response_pool_size:}")
private int responsePoolSize;
@Getter
@Value("${transport.lwm2m.registered_pool_size:}")
private int registeredPoolSize;
@Getter
@Value("${transport.lwm2m.update_registered_pool_size:}")
private int updateRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.un_registered_pool_size:}")
private int unRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.security.key_store_type:}")
private String keyStoreType;
@Getter
@Value("${transport.lwm2m.security.key_store:}")
private String keyStorePathFile;
@Getter
@Setter
private KeyStore keyStoreValue;
@Getter
@Value("${transport.lwm2m.security.key_store_password:}")
private String keyStorePassword;
@Getter
@Value("${transport.lwm2m.security.root_alias:}")
private String rootCertificateAlias;
@Getter
@Value("${transport.lwm2m.security.enable_gen_new_key_psk_rpk:}")
private Boolean enableGenNewKeyPskRpk;
@Getter
@Value("${transport.lwm2m.server.id:}")
private Integer id;
@Getter
@Value("${transport.lwm2m.server.bind_address:}")
private String host;
@Getter
@Value("${transport.lwm2m.server.bind_port:}")
private Integer port;
@Getter
@Value("${transport.lwm2m.server.security.bind_address:}")
private String secureHost;
@Getter
@Value("${transport.lwm2m.server.security.bind_port:}")
private Integer securePort;
@Getter
@Value("${transport.lwm2m.server.security.public_x:}")
private String publicX;
@Getter
@Value("${transport.lwm2m.server.security.public_y:}")
private String publicY;
@Getter
@Value("${transport.lwm2m.server.security.private_encoded:}")
private String privateEncoded;
@Getter
@Value("${transport.lwm2m.server.security.alias:}")
private String certificateAlias;
@Getter
@Value("${transport.lwm2m.log_max_length:}")
private int logMaxLength;
@PostConstruct
public void init() {
URI uri = null;
try {
uri = Resources.getResource(keyStorePathFile).toURI();
log.error("URI: {}", uri);
File keyStoreFile = new File(uri);
InputStream inKeyStore = new FileInputStream(keyStoreFile);
keyStoreValue = KeyStore.getInstance(keyStoreType);
keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray());
} catch (Exception e) {
log.warn("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage());
// Absence of the key store should not block user from using plain LwM2M
// throw new RuntimeException("Failed to lookup LwM2M keystore: " + (uri != null ? uri.toString() : ""), e);
}
}
}

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.secure; package org.thingsboard.server.transport.lwm2m.secure;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.core.util.SecurityUtil;
@ -26,8 +27,10 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -44,13 +47,11 @@ import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X5
@Slf4j @Slf4j
@Component @Component
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@RequiredArgsConstructor
public class LwM2mCredentialsSecurityInfoValidator { public class LwM2mCredentialsSecurityInfoValidator {
private final LwM2mTransportContextServer contextS; private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
public LwM2mCredentialsSecurityInfoValidator(LwM2mTransportContextServer contextS) {
this.contextS = contextS;
}
/** /**
* Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials * Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials
@ -58,17 +59,17 @@ public class LwM2mCredentialsSecurityInfoValidator {
* @param keyValue - * @param keyValue -
* @return ValidateDeviceCredentialsResponseMsg and SecurityInfo * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo
*/ */
public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandler.LwM2mTypeServer keyValue) { public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1];
contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(),
new TransportServiceCallback<>() { new TransportServiceCallback<>() {
@Override @Override
public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
String credentialsBody = msg.getCredentialsBody(); String credentialsBody = msg.getCredentialsBody();
resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue); resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue);
resultSecurityStore[0].setMsg(msg); resultSecurityStore[0].setMsg(msg);
Optional<DeviceProfile> deviceProfileOpt = LwM2mTransportHandler.decode(msg.getProfileBody().toByteArray()); Optional<DeviceProfile> deviceProfileOpt = LwM2mTransportHandlerUtil.decode(msg.getProfileBody().toByteArray());
deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile));
latch.countDown(); latch.countDown();
} }
@ -81,7 +82,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
} }
}); });
try { try {
latch.await(contextS.getLwM2MTransportConfigServer().getTimeout(), TimeUnit.MILLISECONDS); latch.await(config.getTimeout(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.error("Failed to await credentials!", e); log.error("Failed to await credentials!", e);
} }
@ -95,9 +96,9 @@ public class LwM2mCredentialsSecurityInfoValidator {
* @param keyValue - * @param keyValue -
* @return SecurityInfo * @return SecurityInfo
*/ */
private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportHandler.LwM2mTypeServer keyValue) { private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) {
ReadResultSecurityStore result = new ReadResultSecurityStore(); ReadResultSecurityStore result = new ReadResultSecurityStore();
JsonObject objectMsg = LwM2mTransportHandler.validateJson(jsonStr); JsonObject objectMsg = LwM2mTransportHandlerUtil.validateJson(jsonStr);
if (objectMsg != null && !objectMsg.isJsonNull()) { if (objectMsg != null && !objectMsg.isJsonNull()) {
JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null; JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null;
/** /**
@ -108,7 +109,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
&& objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null;
endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk; endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk;
if (object != null && !object.isJsonNull()) { if (object != null && !object.isJsonNull()) {
if (keyValue.equals(LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP)) { if (keyValue.equals(LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP)) {
result.setBootstrapJsonCredential(object); result.setBootstrapJsonCredential(object);
result.setEndPoint(endPoint); result.setEndPoint(endPoint);
result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code);

168
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java

@ -54,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotif
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
@ -84,71 +85,71 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_OBJECT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVICE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertJsonArrayToSet;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getAckCallback;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validateObjectVerFromKey;
@Slf4j @Slf4j
@Service @Service
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
public class LwM2mTransportServiceImpl implements LwM2mTransportService { public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler {
private ExecutorService executorRegistered; private ExecutorService executorRegistered;
private ExecutorService executorUpdateRegistered; private ExecutorService executorUpdateRegistered;
private ExecutorService executorUnRegistered; private ExecutorService executorUnRegistered;
private LwM2mValueConverterImpl converter; private LwM2mValueConverterImpl converter;
private FirmwareDataCache firmwareDataCache;
private final TransportService transportService; private final TransportService transportService;
private final LwM2mTransportContext context;
public final LwM2mTransportContextServer lwM2mTransportContextServer; private final LwM2MTransportServerConfig config;
private final FirmwareDataCache firmwareDataCache;
private final LwM2mTransportServerHelper helper;
private final LwM2mClientContext lwM2mClientContext; private final LwM2mClientContext lwM2mClientContext;
private final LeshanServer leshanServer;
private final LwM2mTransportRequest lwM2mTransportRequest; private final LwM2mTransportRequest lwM2mTransportRequest;
public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer, public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, LwM2mClientContext lwM2mClientContext,
@Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache) { @Lazy LwM2mTransportRequest lwM2mTransportRequest,
FirmwareDataCache firmwareDataCache,
LwM2mTransportContext context) {
this.transportService = transportService; this.transportService = transportService;
this.lwM2mTransportContextServer = lwM2mTransportContextServer; this.config = config;
this.helper = helper;
this.lwM2mClientContext = lwM2mClientContext; this.lwM2mClientContext = lwM2mClientContext;
this.leshanServer = leshanServer;
this.lwM2mTransportRequest = lwM2mTransportRequest; this.lwM2mTransportRequest = lwM2mTransportRequest;
this.firmwareDataCache = firmwareDataCache; this.firmwareDataCache = firmwareDataCache;
this.context = context;
} }
@PostConstruct @PostConstruct
public void init() { public void init() {
this.lwM2mTransportContextServer.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) lwM2mTransportContextServer.getLwM2MTransportConfigServer().getSessionReportTimeout()), lwM2mTransportContextServer.getLwM2MTransportConfigServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS); this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS);
this.executorRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getRegisteredPoolSize(), this.executorRegistered = Executors.newFixedThreadPool(this.config.getRegisteredPoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL)));
this.executorUpdateRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getUpdateRegisteredPoolSize(), this.executorUpdateRegistered = Executors.newFixedThreadPool(this.config.getUpdateRegisteredPoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL)));
this.executorUnRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getUnRegisteredPoolSize(), this.executorUnRegistered = Executors.newFixedThreadPool(this.config.getUnRegisteredPoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL)));
this.converter = LwM2mValueConverterImpl.getInstance(); this.converter = LwM2mValueConverterImpl.getInstance();
} }
@ -278,10 +279,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override @Override
public void setCancelObservations(Registration registration) { public void setCancelObservations(Registration registration) {
if (registration != null) { if (registration != null) {
Set<Observation> observations = leshanServer.getObservationService().getObservations(registration); Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration, observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration,
convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL,
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); null, null, this.config.getTimeout(), null));
} }
} }
@ -333,12 +334,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
msg.getSharedUpdatedList().forEach(tsKvProto -> { msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey(); String pathName = tsKvProto.getKv().getKey();
String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName);
Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); Object valueNew = this.helper.getValueFromKvProto(tsKvProto.getKv());
//TODO: react on change of the firmware name.
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) {
this.getInfoFirmwareUpdate(lwM2MClient); this.getInfoFirmwareUpdate(lwM2MClient);
} }
if (pathIdVer != null) { if (pathIdVer != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config
.getModelProvider()); .getModelProvider());
if (resourceModel != null && resourceModel.operations.isWritable()) { if (resourceModel != null && resourceModel.operations.isWritable()) {
this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer);
@ -359,7 +361,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} else if (msg.getSharedDeletedCount() > 0) { } else if (msg.getSharedDeletedCount() > 0) {
msg.getSharedUpdatedList().forEach(tsKvProto -> { msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey(); String pathName = tsKvProto.getKv().getKey();
Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); Object valueNew = this.helper.getValueFromKvProto(tsKvProto.getKv());
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew); lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew);
} }
@ -403,7 +405,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override @Override
public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) { public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
String idVer = resourceUpdateMsgOpt.get().getResourceKey(); String idVer = resourceUpdateMsgOpt.get().getResourceKey();
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.config.getModelProvider()));
} }
/** /**
@ -412,7 +414,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override @Override
public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) { public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey(); String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey();
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.config.getModelProvider()));
} }
@Override @Override
@ -428,7 +430,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} else { } else {
lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), lwm2mClientRpcRequest.getContentFormatName(), lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), lwm2mClientRpcRequest.getContentFormatName(),
lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(),
this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), lwm2mClientRpcRequest); this.config.getTimeout(), lwm2mClientRpcRequest);
} }
} catch (Exception e) { } catch (Exception e) {
if (lwm2mClientRpcRequest == null) { if (lwm2mClientRpcRequest == null) {
@ -456,7 +458,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
lwm2mClientRpcRequest.setRequestId(toDeviceRequest.getRequestId()); lwm2mClientRpcRequest.setRequestId(toDeviceRequest.getRequestId());
lwm2mClientRpcRequest.setSessionInfo(sessionInfo); lwm2mClientRpcRequest.setSessionInfo(sessionInfo);
lwm2mClientRpcRequest.setValidTypeOper(toDeviceRequest.getMethodName()); lwm2mClientRpcRequest.setValidTypeOper(toDeviceRequest.getMethodName());
JsonObject rpcRequest = LwM2mTransportHandler.validateJson(toDeviceRequest.getParams()); JsonObject rpcRequest = LwM2mTransportHandlerUtil.validateJson(toDeviceRequest.getParams());
if (rpcRequest != null) { if (rpcRequest != null) {
if (rpcRequest.has(lwm2mClientRpcRequest.keyNameKey)) { if (rpcRequest.has(lwm2mClientRpcRequest.keyNameKey)) {
String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, String targetIdVer = this.getPresentPathIntoProfile(sessionInfo,
@ -545,7 +547,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override @Override
public void doTrigger(Registration registration, String path) { public void doTrigger(Registration registration, String path) {
lwM2mTransportRequest.sendAllRequest(registration, path, EXECUTE, lwM2mTransportRequest.sendAllRequest(registration, path, EXECUTE,
ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); ContentFormat.TLV.getName(), null, this.config.getTimeout(), null);
} }
/** /**
@ -578,7 +580,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* *
* @param registration - * @param registration -
*/ */
protected void onAwakeDev(Registration registration) { @Override
public void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId()); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId());
//TODO: associate endpointId with device information. //TODO: associate endpointId with device information.
@ -609,10 +612,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param logMsg - text msg * @param logMsg - text msg
* @param registrationId - Id of Registration LwM2M Client * @param registrationId - Id of Registration LwM2M Client
*/ */
@Override
public void sendLogsToThingsboard(String logMsg, String registrationId) { public void sendLogsToThingsboard(String logMsg, String registrationId) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId);
if (logMsg != null && sessionInfo != null) { if (logMsg != null && sessionInfo != null) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo); if(logMsg.length() > 1024){
logMsg = logMsg.substring(0, 1024);
}
this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvLogyToThingsboard(logMsg), sessionInfo);
} }
} }
@ -632,11 +639,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
if (clientObjects != null && clientObjects.size() > 0) { if (clientObjects != null && clientObjects.size() > 0) {
if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { if (LWM2M_STRATEGY_2 == LwM2mTransportHandlerUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
// #2 // #2
lwM2MClient.getPendingReadRequests().addAll(clientObjects); lwM2MClient.getPendingReadRequests().addAll(clientObjects);
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(),
null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); null, this.config.getTimeout(), null));
} }
// #1 // #1
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects); this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects);
@ -685,7 +692,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/ */
private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config
.getModelProvider())) { .getModelProvider())) {
if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) &&
lwM2MClient.getFrUpdate().getCurrentFwVersion() != null lwM2MClient.getFrUpdate().getCurrentFwVersion() != null
@ -724,10 +731,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (results != null && sessionInfo != null) { if (results != null && sessionInfo != null) {
if (results.getResultAttributes().size() > 0) { if (results.getResultAttributes().size() > 0) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
} }
if (results.getResultTelemetries().size() > 0) { if (results.getResultTelemetries().size() > 0) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -776,7 +783,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
ConcurrentHashMap<String, Object> finalParams = params; ConcurrentHashMap<String, Object> finalParams = params;
pathSend.forEach(target -> { pathSend.forEach(target -> {
lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null);
}); });
if (OBSERVE.equals(typeOper)) { if (OBSERVE.equals(typeOper)) {
lwM2MClient.initReadValue(this, null); lwM2MClient.initReadValue(this, null);
@ -861,7 +868,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null; LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null;
if (resourceValue != null) { if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
Object valueKvProto = null; Object valueKvProto = null;
if (resourceValue.isMultiInstances()) { if (resourceValue.isMultiInstances()) {
valueKvProto = new JsonObject(); valueKvProto = new JsonObject();
@ -878,7 +885,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
} }
return valueKvProto != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null; return valueKvProto != null ? this.helper.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null;
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to add parameters.", e); log.error("Failed to add parameters.", e);
@ -897,7 +904,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) {
LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
} }
@ -1090,10 +1097,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (pathIds.isResource()) { if (pathIds.isResource()) {
if (READ.equals(typeOper)) { if (READ.equals(typeOper)) {
lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, lwM2mTransportRequest.sendAllRequest(registration, target, typeOper,
ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); ContentFormat.TLV.getName(), null, this.config.getTimeout(), null);
} else if (OBSERVE.equals(typeOper)) { } else if (OBSERVE.equals(typeOper)) {
lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, lwM2mTransportRequest.sendAllRequest(registration, target, typeOper,
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); null, null, this.config.getTimeout(), null);
} }
} }
}); });
@ -1149,7 +1156,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (!pathSend.isEmpty()) { if (!pathSend.isEmpty()) {
ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew; ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew;
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); finalParams.get(target), this.config.getTimeout(), null));
} }
}); });
} }
@ -1166,7 +1173,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
params.clear(); params.clear();
params.put(OBJECT_VERSION, ""); params.put(OBJECT_VERSION, "");
lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); params, this.config.getTimeout(), null);
}); });
} }
}); });
@ -1179,7 +1186,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
paramAnallyzer.forEach(pathIdVer -> { paramAnallyzer.forEach(pathIdVer -> {
if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) {
lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null, lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null,
null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); null, this.config.getTimeout(), null);
} }
} }
); );
@ -1189,7 +1196,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) {
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE, lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE,
ContentFormat.TLV.getName(), valueNew, ContentFormat.TLV.getName(), valueNew,
this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); this.config.getTimeout(), null);
} else { } else {
log.error("Failed update resource [{}] [{}]", path, valueNew); log.error("Failed update resource [{}] [{}]", path, valueNew);
String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad",
@ -1267,7 +1274,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
// #2.1 // #2.1
lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> {
this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer),
this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()), pathIdVer); this.helper.getValueFromKvProto(tsKvProto.getKv()), pathIdVer);
}); });
} }
@ -1284,7 +1291,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
return null; return null;
} else { } else {
return SessionInfoProto.newBuilder() return SessionInfoProto.newBuilder()
.setNodeId(this.lwM2mTransportContextServer.getNodeId()) .setNodeId(this.context.getNodeId())
.setSessionIdMSB(lwM2MClient.getSessionId().getMostSignificantBits()) .setSessionIdMSB(lwM2MClient.getSessionId().getMostSignificantBits())
.setSessionIdLSB(lwM2MClient.getSessionId().getLeastSignificantBits()) .setSessionIdLSB(lwM2MClient.getSessionId().getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
@ -1354,7 +1361,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (keyNamesMap.values().size() > 0) { if (keyNamesMap.values().size() > 0) {
try { try {
//#1.2 //#1.2
TransportProtos.GetAttributeRequestMsg getAttributeMsg = lwM2mTransportContextServer.getAdaptor().convertToGetAttributes(null, keyNamesMap.values()); TransportProtos.GetAttributeRequestMsg getAttributeMsg = helper.getAdaptor().convertToGetAttributes(null, keyNamesMap.values());
transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST));
} catch (AdaptorException e) { } catch (AdaptorException e) {
log.warn("Failed to decode get attributes request", e); log.warn("Failed to decode get attributes request", e);
@ -1372,6 +1379,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) .setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB()) .setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB()) .setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setType(FirmwareType.FIRMWARE.name())
.build(); .build();
transportService.process(sessionInfo, getFirmwareRequestMsg, transportService.process(sessionInfo, getFirmwareRequestMsg,
new TransportServiceCallback<>() { new TransportServiceCallback<>() {
@ -1401,7 +1409,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
public void readRequestToClientFirmwareVer(Registration registration) { public void readRequestToClientFirmwareVer(Registration registration) {
String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration); String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration);
lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(), lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(),
null, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); null, config.getTimeout(), null);
} }
/** /**
@ -1413,11 +1421,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
int chunkSize = 0; int chunkSize = 0;
int chunk = 0; int chunk = 0;
byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk); byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk);
Integer objectId = 5; String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(FR_OBJECT_ID);
String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId); String targetIdVer = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0;
String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.TLV.getName(), firmwareChunk, config.getTimeout(), null);
firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null);
log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion()); log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion());
} }
} }
@ -1439,7 +1446,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} }
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config
.getModelProvider()); .getModelProvider());
Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathIdVer); String objectVer = validateObjectVerFromKey(pathIdVer);
@ -1448,9 +1455,4 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
} }
@Override
public String getName() {
return "LWM2M";
}
} }

152
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.core.network.stack.BlockwiseLayer; import org.eclipse.californium.core.network.stack.BlockwiseLayer;
@ -29,12 +30,16 @@ import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.security.DefaultAuthorizer; import org.eclipse.leshan.server.security.DefaultAuthorizer;
import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityChecker;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
import java.security.KeyFactory; import java.security.KeyFactory;
@ -64,60 +69,81 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
@Slf4j @Slf4j
@Component @Component
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
public class LwM2mTransportServerConfiguration { @RequiredArgsConstructor
public class DefaultLwM2mTransportService implements LwM2MTransportService {
private PublicKey publicKey; private PublicKey publicKey;
private PrivateKey privateKey; private PrivateKey privateKey;
private boolean pskMode = false; private boolean pskMode = false;
private final LwM2mTransportContextServer context;
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final LwM2mTransportServerHelper helper;
private final LwM2mTransportMsgHandler handler;
private final CaliforniumRegistrationStore registrationStore; private final CaliforniumRegistrationStore registrationStore;
private final EditableSecurityStore securityStore; private final EditableSecurityStore securityStore;
private final LwM2mClientContext lwM2mClientContext; private final LwM2mClientContext lwM2mClientContext;
public LwM2mTransportServerConfiguration(LwM2mTransportContextServer context, CaliforniumRegistrationStore registrationStore, EditableSecurityStore securityStore, LwM2mClientContext lwM2mClientContext) { private LeshanServer server;
this.context = context;
this.registrationStore = registrationStore; @PostConstruct
this.securityStore = securityStore; public void init() {
this.lwM2mClientContext = lwM2mClientContext; if (config.getEnableGenNewKeyPskRpk()) {
new LWM2MGenerationPSkRPkECC();
}
this.server = getLhServer(config.getPort(), config.getSecurePort());
this.startLhServer();
this.context.setServer(server);
} }
@Bean private void startLhServer() {
public LeshanServer getLeshanServer() { log.info("Starting LwM2M transport Server...");
log.info("Starting LwM2M transport Server... PostConstruct"); this.server.start();
return this.getLhServer(this.context.getLwM2MTransportConfigServer().getServerPortNoSec(), this.context.getLwM2MTransportConfigServer().getServerPortSecurity()); LwM2mServerListener lhServerCertListener = new LwM2mServerListener(handler);
this.server.getRegistrationService().addListener(lhServerCertListener.registrationListener);
this.server.getPresenceService().addListener(lhServerCertListener.presenceListener);
this.server.getObservationService().addListener(lhServerCertListener.observationListener);
}
@PreDestroy
public void shutdown() {
log.info("Stopping LwM2M transport Server!");
server.destroy();
log.info("LwM2M transport Server stopped!");
} }
private LeshanServer getLhServer(Integer serverPortNoSec, Integer serverSecurePort) { private LeshanServer getLhServer(Integer serverPortNoSec, Integer serverSecurePort) {
LeshanServerBuilder builder = new LeshanServerBuilder(); LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(this.context.getLwM2MTransportConfigServer().getServerHost(), serverPortNoSec); builder.setLocalAddress(config.getHost(), serverPortNoSec);
builder.setLocalSecureAddress(this.context.getLwM2MTransportConfigServer().getServerHostSecurity(), serverSecurePort); builder.setLocalSecureAddress(config.getSecureHost(), serverSecurePort);
builder.setDecoder(new DefaultLwM2mNodeDecoder()); builder.setDecoder(new DefaultLwM2mNodeDecoder());
/** Use a magic converter to support bad type send by the UI. */ /* Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/** Create CoAP Config */ /* Create CoAP Config */
NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort); NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort);
BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig); BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig);
builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort)); builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort));
/** Define model provider (Create Models )*/ /* Define model provider (Create Models )*/
LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.context); LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.helper, this.context);
this.context.getLwM2MTransportConfigServer().setModelProvider(modelProvider); config.setModelProvider(modelProvider);
builder.setObjectModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider);
/** Create credentials */ /* Create credentials */
this.setServerWithCredentials(builder); this.setServerWithCredentials(builder);
/** Set securityStore with new registrationStore */ /* Set securityStore with new registrationStore */
builder.setSecurityStore(securityStore); builder.setSecurityStore(securityStore);
builder.setRegistrationStore(registrationStore); builder.setRegistrationStore(registrationStore);
/** Create DTLS Config */ /* Create DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setServerOnly(true); dtlsConfig.setServerOnly(true);
dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportConfigServer().isRecommendedSupportedGroups()); dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportConfigServer().isRecommendedCiphers()); dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers());
if (this.pskMode) { if (this.pskMode) {
dtlsConfig.setSupportedCipherSuites( dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CCM_8,
@ -130,27 +156,27 @@ public class LwM2mTransportServerConfiguration {
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
} }
/** Set DTLS Config */ /* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
/** Create LWM2M server */ /* Create LWM2M server */
return builder.build(); return builder.build();
} }
private void setServerWithCredentials(LeshanServerBuilder builder) { private void setServerWithCredentials(LeshanServerBuilder builder) {
try { try {
if (this.context.getLwM2MTransportConfigServer().getKeyStoreValue() != null) { if (config.getKeyStoreValue() != null) {
if (this.setBuilderX509(builder)) { if (this.setBuilderX509(builder)) {
X509Certificate rootCAX509Cert = (X509Certificate) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportConfigServer().getRootAlias()); X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias());
if (rootCAX509Cert != null) { if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1]; X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert; trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates); builder.setTrustedCertificates(trustedCertificates);
} else { } else {
/** by default trust all */ /* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]); builder.setTrustedCertificates(new X509Certificate[0]);
} }
/** Set securityStore with registrationStore*/ /* Set securityStore with registrationStore*/
builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
@Override @Override
protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
@ -163,7 +189,7 @@ public class LwM2mTransportServerConfiguration {
this.infoPramsUri("RPK"); this.infoPramsUri("RPK");
this.infoParamsServerKey(this.publicKey, this.privateKey); this.infoParamsServerKey(this.publicKey, this.privateKey);
} else { } else {
/** by default trust all */ /* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]); builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for LWM2MServer"); log.info("Unable to load X509 files for LWM2MServer");
this.pskMode = true; this.pskMode = true;
@ -175,17 +201,11 @@ public class LwM2mTransportServerConfiguration {
} }
private boolean setBuilderX509(LeshanServerBuilder builder) { private boolean setBuilderX509(LeshanServerBuilder builder) {
/**
* For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE
* For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks
*/
try { try {
X509Certificate serverCertificate = (X509Certificate) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportConfigServer().getServerAlias()); X509Certificate serverCertificate = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getCertificateAlias());
PrivateKey privateKey = (PrivateKey) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getKey(this.context.getLwM2MTransportConfigServer().getServerAlias(), this.context.getLwM2MTransportConfigServer().getKeyStorePasswordServer() == null ? null : this.context.getLwM2MTransportConfigServer().getKeyStorePasswordServer().toCharArray()); PrivateKey privateKey = (PrivateKey) config.getKeyStoreValue().getKey(config.getCertificateAlias(), config.getKeyStorePassword() == null ? null : config.getKeyStorePassword().toCharArray());
PublicKey publicKey = serverCertificate.getPublicKey(); PublicKey publicKey = serverCertificate.getPublicKey();
if (serverCertificate != null && if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) {
privateKey != null && privateKey.getEncoded().length > 0 &&
publicKey != null && publicKey.getEncoded().length > 0) {
builder.setPublicKey(serverCertificate.getPublicKey()); builder.setPublicKey(serverCertificate.getPublicKey());
builder.setPrivateKey(privateKey); builder.setPrivateKey(privateKey);
builder.setCertificateChain(new X509Certificate[]{serverCertificate}); builder.setCertificateChain(new X509Certificate[]{serverCertificate});
@ -212,10 +232,12 @@ public class LwM2mTransportServerConfiguration {
} }
private void infoPramsUri(String mode) { private void infoPramsUri(String mode) {
log.info("Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]", LwM2MTransportServerConfig lwM2MTransportServerConfig = config;
mode, log.info("Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode,
this.context.getLwM2MTransportConfigServer().getServerHost() + ":" + this.context.getLwM2MTransportConfigServer().getServerPortNoSec(), lwM2MTransportServerConfig.getHost(),
this.context.getLwM2MTransportConfigServer().getServerHostSecurity() + ":" + this.context.getLwM2MTransportConfigServer().getServerPortSecurity()); lwM2MTransportServerConfig.getPort(),
lwM2MTransportServerConfig.getSecureHost(),
lwM2MTransportServerConfig.getSecurePort());
} }
private boolean setServerRPK(LeshanServerBuilder builder) { private boolean setServerRPK(LeshanServerBuilder builder) {
@ -233,55 +255,42 @@ public class LwM2mTransportServerConfiguration {
return false; return false;
} }
/**
* From yml: server
* public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}"
* public_y: "${LWM2M_SERVER_PUBLIC_Y:30c9237e946a3a1692c1cafaa01a238a077f632c99371348337512363f28212b}"
* private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:274671fe40ce937b8a6352cf0a418e8a39e4bf0bb9bf74c910db953c20c73802}"
*/
private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException {
/** Get Elliptic Curve Parameter spec for secp256r1 */ /* Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1")); algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.context.getLwM2MTransportConfigServer().getServerPublicX() != null && LwM2MTransportServerConfig serverConfig = config;
!this.context.getLwM2MTransportConfigServer().getServerPublicX().isEmpty() && if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) {
this.context.getLwM2MTransportConfigServer().getServerPublicY() != null && byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray());
!this.context.getLwM2MTransportConfigServer().getServerPublicY().isEmpty()) { byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray());
/** Get point values */
byte[] publicX = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPublicY().toCharArray());
/** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec); parameterSpec);
/** Get keys */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
} }
if (this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded() != null && String privateEncodedKey = serverConfig.getPrivateEncoded();
!this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded().isEmpty()) { if (StringUtils.isNotEmpty(privateEncodedKey)) {
/** Get private key */ byte[] privateS = Hex.decodeHex(privateEncodedKey.toCharArray());
byte[] privateS = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded().toCharArray());
try { try {
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS)); this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS));
} catch (InvalidKeySpecException ignore2) { } catch (InvalidKeySpecException ignore2) {
log.error("Invalid Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded()); log.error("Invalid Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", privateEncodedKey);
} }
} }
} }
private void infoParamsServerKey(PublicKey publicKey, PrivateKey privateKey) { private void infoParamsServerKey(PublicKey publicKey, PrivateKey privateKey) {
/** Get x coordinate */ /* Get x coordinate */
byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray(); byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray();
if (x[0] == 0) if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length); x = Arrays.copyOfRange(x, 1, x.length);
/** Get Y coordinate */ /* Get Y coordinate */
byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray(); byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray();
if (y[0] == 0) if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length); y = Arrays.copyOfRange(y, 1, y.length);
/** Get Curves params */ /* Get Curves params */
String params = ((ECPublicKey) publicKey).getParams().toString(); String params = ((ECPublicKey) publicKey).getParams().toString();
String privHex = Hex.encodeHexString(privateKey.getEncoded()); String privHex = Hex.encodeHexString(privateKey.getEncoded());
log.info(" \n- Public Key (Hex): [{}] \n" + log.info(" \n- Public Key (Hex): [{}] \n" +
@ -298,4 +307,9 @@ public class LwM2mTransportServerConfiguration {
params); params);
} }
@Override
public String getName() {
return "LWM2M";
}
} }

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server;
import org.thingsboard.server.common.data.TbTransportService;
public interface LwM2MTransportService extends TbTransportService {
}

111
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java

@ -23,75 +23,74 @@ public class LwM2mNetworkConfig {
NetworkConfig coapConfig = new NetworkConfig(); NetworkConfig coapConfig = new NetworkConfig();
coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec); coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec);
coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort); coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort);
/** /*
* Example:Property for large packet: Example:Property for large packet:
* #NetworkConfig config = new NetworkConfig(); #NetworkConfig config = new NetworkConfig();
* #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32); #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32);
* #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32); #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32);
* #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048); #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048);
* #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3); #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3);
* #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000); #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000);
*/ */
/** /*
* Property to indicate if the response should always include the Block2 option \ Property to indicate if the response should always include the Block2 option \
* when client request early blockwise negociation but the response can be sent on one packet. when client request early blockwise negociation but the response can be sent on one packet.
* - value of false indicate that the server will respond without block2 option if no further blocks are required. - value of false indicate that the server will respond without block2 option if no further blocks are required.
* - value of true indicate that the server will response with block2 option event if no further blocks are required. - value of true indicate that the server will response with block2 option event if no further blocks are required.
* CoAP client will try to use block mode CoAP client will try to use block mode
* or adapt the block size when receiving a 4.13 Entity too large response code or adapt the block size when receiving a 4.13 Entity too large response code
*/ */
coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
/*** /*
* Property to indicate if the response should always include the Block2 option \ Property to indicate if the response should always include the Block2 option \
* when client request early blockwise negociation but the response can be sent on one packet. when client request early blockwise negociation but the response can be sent on one packet.
* - value of false indicate that the server will respond without block2 option if no further blocks are required. - value of false indicate that the server will respond without block2 option if no further blocks are required.
* - value of true indicate that the server will response with block2 option event if no further blocks are required. - value of true indicate that the server will response with block2 option event if no further blocks are required.
*/ */
coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000); coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000);
/** /*
* !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13 !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13
* The maximum size of a resource body (in bytes) that will be accepted The maximum size of a resource body (in bytes) that will be accepted
* as the payload of a POST/PUT or the response to a GET request in a as the payload of a POST/PUT or the response to a GET request in a
* transparent> blockwise transfer. transparent> blockwise transfer.
* This option serves as a safeguard against excessive memory This option serves as a safeguard against excessive memory
* consumption when many resources contain large bodies that cannot be consumption when many resources contain large bodies that cannot be
* transferred in a single CoAP message. This option has no impact on transferred in a single CoAP message. This option has no impact on
* *manually* managed blockwise transfers in which the blocks are handled individually. *manually* managed blockwise transfers in which the blocks are handled individually.
* Note that this option does not prevent local clients or resource Note that this option does not prevent local clients or resource
* implementations from sending large bodies as part of a request or response to a peer. implementations from sending large bodies as part of a request or response to a peer.
* The default value of this property is DEFAULT_MAX_RESOURCE_BODY_SIZE = 8192 The default value of this property is DEFAULT_MAX_RESOURCE_BODY_SIZE = 8192
* A value of {@code 0} turns off transparent handling of blockwise transfers altogether. A value of {@code 0} turns off transparent handling of blockwise transfers altogether.
*/ */
// coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 8192); coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024);
coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 16384); /*
/** The default DTLS response matcher.
* The default DTLS response matcher. Supported values are STRICT, RELAXED, or PRINCIPAL.
* Supported values are STRICT, RELAXED, or PRINCIPAL. The default value is STRICT.
* The default value is STRICT. Create new instance of udp endpoint context matcher.
* Create new instance of udp endpoint context matcher. Params:
* Params: checkAddress
* checkAddress – true with address check, (STRICT, UDP)
* – true with address check, (STRICT, UDP) - false, without
* - false, without
*/ */
coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT"); coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT");
/** /*
* https://tools.ietf.org/html/rfc7959#section-2.9.3 https://tools.ietf.org/html/rfc7959#section-2.9.3
* The block size (number of bytes) to use when doing a blockwise transfer. \ The block size (number of bytes) to use when doing a blockwise transfer. \
* This value serves as the upper limit for block size in blockwise transfers This value serves as the upper limit for block size in blockwise transfers
*/ */
coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 512); coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024);
/** /*
* The maximum payload size (in bytes) that can be transferred in a The maximum payload size (in bytes) that can be transferred in a
* single message, i.e. without requiring a blockwise transfer. single message, i.e. without requiring a blockwise transfer.
* NB: this value MUST be adapted to the maximum message size supported by the transport layer. NB: this value MUST be adapted to the maximum message size supported by the transport layer.
* In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol
* DEFAULT_VALUE = 1024 DEFAULT_VALUE = 1024
*/ */
coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 512); coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024);
coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4);

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java

@ -26,15 +26,15 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate;
import java.util.Collection; import java.util.Collection;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
@Slf4j @Slf4j
public class LwM2mServerListener { public class LwM2mServerListener {
private final LwM2mTransportServiceImpl service; private final LwM2mTransportMsgHandler service;
public LwM2mServerListener(LwM2mTransportServiceImpl service) { public LwM2mServerListener(LwM2mTransportMsgHandler service) {
this.service = service; this.service = service;
} }

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java

@ -35,10 +35,10 @@ import java.util.Optional;
@Slf4j @Slf4j
public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener { public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
private LwM2mTransportServiceImpl service; private DefaultLwM2MTransportMsgHandler service;
private TransportProtos.SessionInfoProto sessionInfo; private TransportProtos.SessionInfoProto sessionInfo;
public LwM2mSessionMsgListener(LwM2mTransportServiceImpl service, TransportProtos.SessionInfoProto sessionInfo) { public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler service, TransportProtos.SessionInfoProto sessionInfo) {
this.service = service; this.service = service;
this.sessionInfo = sessionInfo; this.sessionInfo = sessionInfo;
} }

32
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server;
import lombok.Getter;
import lombok.Setter;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
@Component
@TbLwM2mTransportComponent
public class LwM2mTransportContext extends TransportContext {
@Getter @Setter
private LeshanServer server;
}

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java

@ -67,7 +67,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
@Slf4j @Slf4j
public class LwM2mTransportHandler { public class LwM2mTransportHandlerUtil {
public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0"; public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
@ -256,7 +256,7 @@ public class LwM2mTransportHandler {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
String profileStr = mapper.writeValueAsString(profile); String profileStr = mapper.writeValueAsString(profile);
JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandlerUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
} catch (IOException e) { } catch (IOException e) {
log.error("", e); log.error("", e);
} }

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java

@ -27,7 +27,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcReques
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
public interface LwM2mTransportService extends TbTransportService { public interface LwM2mTransportMsgHandler {
void onRegistered(Registration registration, Collection<Observation> previousObsersations); void onRegistered(Registration registration, Collection<Observation> previousObsersations);
@ -60,4 +60,8 @@ public interface LwM2mTransportService extends TbTransportService {
void doTrigger(Registration registration, String path); void doTrigger(Registration registration, String path);
void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);
void onAwakeDev(Registration registration);
void sendLogsToThingsboard(String msg, String registrationId);
} }

108
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.CoAP;
@ -50,6 +51,7 @@ import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest;
@ -66,51 +68,38 @@ import java.util.stream.Collectors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT;
import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.createWriteAttributeRequest;
@Slf4j @Slf4j
@Service @Service
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@RequiredArgsConstructor
public class LwM2mTransportRequest { public class LwM2mTransportRequest {
private ExecutorService executorResponse; private ExecutorService executorResponse;
public LwM2mValueConverterImpl converter; public LwM2mValueConverterImpl converter;
private final LwM2mTransportContextServer lwM2mTransportContextServer; private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final LwM2mTransportServerHelper lwM2MTransportServerHelper;
private final LwM2mClientContext lwM2mClientContext; private final LwM2mClientContext lwM2mClientContext;
private final DefaultLwM2MTransportMsgHandler serviceImpl;
private final LeshanServer leshanServer;
private final LwM2mTransportServiceImpl serviceImpl;
private final TransportService transportService;
public LwM2mTransportRequest(LwM2mTransportContextServer lwM2mTransportContextServer,
LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer,
LwM2mTransportServiceImpl serviceImpl, TransportService transportService) {
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
this.lwM2mClientContext = lwM2mClientContext;
this.leshanServer = leshanServer;
this.serviceImpl = serviceImpl;
this.transportService = transportService;
}
@PostConstruct @PostConstruct
public void init() { public void init() {
this.converter = LwM2mValueConverterImpl.getInstance(); this.converter = LwM2mValueConverterImpl.getInstance();
executorResponse = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResponsePoolSize(), executorResponse = Executors.newFixedThreadPool(this.config.getResponsePoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL)));
} }
@ -158,10 +147,10 @@ public class LwM2mTransportRequest {
* At server side this will not remove the observation from the observation store, to do it you need to use * At server side this will not remove the observation from the observation store, to do it you need to use
* {@code ObservationService#cancelObservation()} * {@code ObservationService#cancelObservation()}
*/ */
leshanServer.getObservationService().cancelObservations(registration, target); context.getServer().getObservationService().cancelObservations(registration, target);
break; break;
case EXECUTE: case EXECUTE:
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config
.getModelProvider()); .getModelProvider());
if (params != null && !resourceModel.multiple) { if (params != null && !resourceModel.multiple) {
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds)); request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds));
@ -171,7 +160,7 @@ public class LwM2mTransportRequest {
break; break;
case WRITE_REPLACE: case WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format. // Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config
.getModelProvider()); .getModelProvider());
if (contentFormat.equals(ContentFormat.TLV)) { if (contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(),
@ -232,7 +221,7 @@ public class LwM2mTransportRequest {
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
} }
} else if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { } else if (OBSERVE_READ_ALL.name().equals(typeOper.name())) {
Set<Observation> observations = leshanServer.getObservationService().getObservations(registration); Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
Set<String> observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); Set<String> observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet());
String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO, String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO,
OBSERVE_READ_ALL.type, observationPaths); OBSERVE_READ_ALL.type, observationPaths);
@ -259,7 +248,7 @@ public class LwM2mTransportRequest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) {
leshanServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> { context.getServer().send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) { if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
} }
@ -415,17 +404,40 @@ public class LwM2mTransportRequest {
} }
} }
private void infoWriteResponse(Registration registration, LwM2mResponse response, private void infoWriteResponse(Registration registration, LwM2mResponse response, DownlinkRequest request) {
DownlinkRequest request) { try {
LwM2mNode node = ((WriteRequest) request).getNode(); LwM2mNode node = ((WriteRequest) request).getNode();
Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(), String msg;
((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath()); Object value;
String msg = String.format("%s: Update finished successfully: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s", LwM2mSingleResource singleResource = (LwM2mSingleResource) node;
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), if (singleResource.getType() == ResourceModel.Type.STRING || singleResource.getType() == ResourceModel.Type.OPAQUE) {
response.getCode().getName(), request.getPath().toString(), value); int valueLength;
serviceImpl.sendLogsToThingsboard(msg, registration.getId()); if (singleResource.getType() == ResourceModel.Type.STRING) {
log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(), valueLength = ((String) singleResource.getValue()).length();
((Response) response.getCoapResponse()).getCode(), response.getCode(), value = ((String) singleResource.getValue())
request.getPath().toString(), value); .substring(Math.min(valueLength, config.getLogMaxLength()));
} else {
valueLength = ((byte[]) singleResource.getValue()).length;
value = new String(Arrays.copyOf(((byte[]) singleResource.getValue()),
Math.min(valueLength, config.getLogMaxLength())));
}
value = valueLength > config.getLogMaxLength() ? value + "..." : value;
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s length - %s value - %s",
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), valueLength, value);
} else {
value = this.converter.convertValue(singleResource.getValue(),
singleResource.getType(), ResourceModel.Type.STRING, request.getPath());
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s value - %s",
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value);
}
if (msg != null) {
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), value);
}
} catch (Exception e) {
log.trace("Fail convert value from request to string. ", e);
}
} }
} }

85
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -31,6 +31,7 @@ package org.thingsboard.server.transport.lwm2m.server;
*/ */
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser; import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
@ -39,11 +40,8 @@ import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.CodecException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportResourceCache;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
@ -57,39 +55,21 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V; import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_TELEMETRY;
@Slf4j @Slf4j
@Component @Component
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
public class LwM2mTransportContextServer extends TransportContext { @RequiredArgsConstructor
public class LwM2mTransportServerHelper {
private final LwM2mTransportContext context;
private final LwM2MTransportConfigServer lwM2MTransportConfigServer;
private final TransportService transportService; private final TransportService transportService;
private final TransportResourceCache transportResourceCache;
@Getter @Getter
private final LwM2MJsonAdaptor adaptor; private final LwM2MJsonAdaptor adaptor;
public LwM2mTransportContextServer(LwM2MTransportConfigServer lwM2MTransportConfigServer, TransportService transportService, TransportResourceCache transportResourceCache, LwM2MJsonAdaptor adaptor) {
this.lwM2MTransportConfigServer = lwM2MTransportConfigServer;
this.transportService = transportService;
this.transportResourceCache = transportResourceCache;
this.adaptor = adaptor;
}
public LwM2MTransportConfigServer getLwM2MTransportConfigServer() {
return this.lwM2MTransportConfigServer;
}
public TransportResourceCache getTransportResourceCache() {
return this.transportResourceCache;
}
/** /**
* send to Thingsboard Attribute || Telemetry * send to Thingsboard Attribute || Telemetry
* *
@ -134,7 +114,7 @@ public class LwM2mTransportContextServer extends TransportContext {
*/ */
public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
return SessionInfoProto.newBuilder() return SessionInfoProto.newBuilder()
.setNodeId(this.getNodeId()) .setNodeId(context.getNodeId())
.setSessionIdMSB(mostSignificantBits) .setSessionIdMSB(mostSignificantBits)
.setSessionIdLSB(leastSignificantBits) .setSessionIdLSB(leastSignificantBits)
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
@ -165,8 +145,8 @@ public class LwM2mTransportContextServer extends TransportContext {
* @param logMsg - info about Logs * @param logMsg - info about Logs
* @return- KeyValueProto for telemetry (Logs) * @return- KeyValueProto for telemetry (Logs)
*/ */
public List <TransportProtos.KeyValueProto> getKvLogyToThingsboard(String logMsg) { public List<TransportProtos.KeyValueProto> getKvLogyToThingsboard(String logMsg) {
List <TransportProtos.KeyValueProto> result = new ArrayList<>(); List<TransportProtos.KeyValueProto> result = new ArrayList<>();
result.add(TransportProtos.KeyValueProto.newBuilder() result.add(TransportProtos.KeyValueProto.newBuilder()
.setKey(LOG_LW2M_TELEMETRY) .setKey(LOG_LW2M_TELEMETRY)
.setType(TransportProtos.KeyValueType.STRING_V) .setType(TransportProtos.KeyValueType.STRING_V)
@ -179,32 +159,31 @@ public class LwM2mTransportContextServer extends TransportContext {
* @throws CodecException - * @throws CodecException -
*/ */
public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) { public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) {
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName); TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName);
if (isMultiInstances) { if (isMultiInstances) {
kvProto.setType(TransportProtos.KeyValueType.JSON_V) kvProto.setType(TransportProtos.KeyValueType.JSON_V)
.setJsonV((String) value); .setJsonV((String) value);
} else {
switch (resourceType) {
case BOOLEAN:
kvProto.setType(BOOLEAN_V).setBoolV((Boolean) value).build();
break;
case STRING:
case TIME:
case OPAQUE:
case OBJLNK:
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV((String) value);
break;
case INTEGER:
kvProto.setType(TransportProtos.KeyValueType.LONG_V).setLongV((Long) value);
break;
case FLOAT:
kvProto.setType(TransportProtos.KeyValueType.DOUBLE_V).setDoubleV((Double) value);
} }
else {
switch (resourceType) {
case BOOLEAN:
kvProto.setType(BOOLEAN_V).setBoolV((Boolean) value).build();
break;
case STRING:
case TIME:
case OPAQUE:
case OBJLNK:
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV((String) value);
break;
case INTEGER:
kvProto.setType(TransportProtos.KeyValueType.LONG_V).setLongV((Long) value);
break;
case FLOAT:
kvProto.setType(TransportProtos.KeyValueType.DOUBLE_V).setDoubleV((Double) value);
}
}
return kvProto.build();
} }
return kvProto.build();
}
/** /**
* *
@ -230,7 +209,7 @@ public class LwM2mTransportContextServer extends TransportContext {
throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType); throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType);
} }
public Object getValueFromKvProto (TransportProtos.KeyValueProto kv) { public Object getValueFromKvProto(TransportProtos.KeyValueProto kv) {
switch (kv.getType()) { switch (kv.getType()) {
case BOOLEAN_V: case BOOLEAN_V:
return kv.getBoolV(); return kv.getBoolV();

31
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java

@ -30,35 +30,4 @@ import javax.annotation.PreDestroy;
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
public class LwM2mTransportServerInitializer { public class LwM2mTransportServerInitializer {
@Autowired
private LwM2mTransportServiceImpl service;
@Autowired
private LeshanServer leshanServer;
@Autowired
private LwM2mTransportContextServer context;
@PostConstruct
public void init() {
if (this.context.getLwM2MTransportConfigServer().getEnableGenNewKeyPskRpk()) {
new LWM2MGenerationPSkRPkECC();
}
this.startLhServer();
}
private void startLhServer() {
this.leshanServer.start();
LwM2mServerListener lhServerCertListener = new LwM2mServerListener(service);
this.leshanServer.getRegistrationService().addListener(lhServerCertListener.registrationListener);
this.leshanServer.getPresenceService().addListener(lhServerCertListener.presenceListener);
this.leshanServer.getObservationService().addListener(lhServerCertListener.observationListener);
}
@PreDestroy
public void shutdown() {
log.info("Stopping LwM2M transport Server!");
leshanServer.destroy();
log.info("LwM2M transport Server stopped!");
}
} }

16
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.LwM2mModel;
@ -37,6 +38,7 @@ import static org.thingsboard.server.common.data.ResourceType.LWM2M_MODEL;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
@Slf4j @Slf4j
@RequiredArgsConstructor
public class LwM2mVersionedModelProvider implements LwM2mModelProvider { public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
/** /**
@ -46,12 +48,8 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
* Value = TenantId * Value = TenantId
*/ */
private final LwM2mClientContext lwM2mClientContext; private final LwM2mClientContext lwM2mClientContext;
private final LwM2mTransportContextServer lwM2mTransportContextServer; private final LwM2mTransportServerHelper helper;
private final LwM2mTransportContext context;
public LwM2mVersionedModelProvider(LwM2mClientContext lwM2mClientContext, LwM2mTransportContextServer lwM2mTransportContextServer) {
this.lwM2mClientContext = lwM2mClientContext;
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
}
private String getKeyIdVer(Integer objectId, String version) { private String getKeyIdVer(Integer objectId, String version) {
return objectId != null ? objectId + LWM2M_SEPARATOR_KEY + ((version == null || version.isEmpty()) ? ObjectModel.DEFAULT_VERSION : version) : null; return objectId != null ? objectId + LWM2M_SEPARATOR_KEY + ((version == null || version.isEmpty()) ? ObjectModel.DEFAULT_VERSION : version) : null;
@ -120,11 +118,9 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
private ObjectModel getObjectModelDynamic(Integer objectId, String version) { private ObjectModel getObjectModelDynamic(Integer objectId, String version) {
String key = getKeyIdVer(objectId, version); String key = getKeyIdVer(objectId, version);
Optional<TbResource> tbResource = lwM2mTransportContextServer Optional<TbResource> tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key);
.getTransportResourceCache()
.get(this.tenantId, LWM2M_MODEL, key);
return tbResource.map(resource -> lwM2mTransportContextServer.parseFromXmlToObjectModel( return tbResource.map(resource -> helper.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()), Base64.getDecoder().decode(resource.getData()),
key + ".xml", key + ".xml",
new DefaultDDFFileValidator())).orElse(null); new DefaultDDFFileValidator())).orElse(null);

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -27,7 +27,7 @@ import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.Collection; import java.util.Collection;
@ -42,9 +42,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.TRANSPORT_DEFAULT_LWM2M_VERSION; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.TRANSPORT_DEFAULT_LWM2M_VERSION;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getVerFromPathIdVerOrId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getVerFromPathIdVerOrId;
@Slf4j @Slf4j
@Data @Data
@ -172,7 +172,7 @@ public class LwM2mClient implements Cloneable {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public void initReadValue(LwM2mTransportServiceImpl serviceImpl, String path) { public void initReadValue(DefaultLwM2MTransportMsgHandler serviceImpl, String path) {
if (path != null) { if (path != null) {
this.pendingReadRequests.remove(path); this.pendingReadRequests.remove(path);
} }

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -25,7 +25,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
@ -34,7 +34,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
@Service @Service
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@ -118,7 +118,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
*/ */
@Override @Override
public LwM2mClient addLwM2mClientToSession(String identity) { public LwM2mClient addLwM2mClientToSession(String identity) {
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandler.LwM2mTypeServer.CLIENT); ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandlerUtil.LwM2mTypeServer.CLIENT);
if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null;
LwM2mClient client; LwM2mClient client;
@ -165,7 +165,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override @Override
public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandler.getLwM2MClientProfileFromThingsboard(deviceProfile); LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandlerUtil.getLwM2MClientProfileFromThingsboard(deviceProfile);
if (lwM2MClientProfile != null) { if (lwM2MClientProfile != null) {
profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile);
return true; return true;

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java

@ -21,11 +21,11 @@ import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validPathIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validPathIdVer;
@Data @Data
public class Lwm2mClientRpcRequest { public class Lwm2mClientRpcRequest {

0
common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh

0
common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks → common/transport/lwm2m/src/main/resources/lwm2mserver.jks

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java

@ -51,11 +51,13 @@ public abstract class TransportContext {
@Getter @Getter
private ExecutorService executor; private ExecutorService executor;
@Getter @Getter
@Autowired @Autowired
private FirmwareDataCache firmwareDataCache; private FirmwareDataCache firmwareDataCache;
@Autowired
private TransportResourceCache transportResourceCache;
@PostConstruct @PostConstruct
public void init() { public void init() {
executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass()); executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass());

217
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java

@ -1,217 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.lwm2m;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportConfigServer {
@Getter
private String KEY_STORE_DEFAULT_RESOURCE_PATH = "credentials";
@Getter
private String KEY_STORE_DEFAULT_FILE = "serverKeyStore.jks";
@Getter
private String APP_DIR = "common";
@Getter
private String TRANSPORT_DIR = "transport";
@Getter
private String LWM2M_DIR = "lwm2m";
@Getter
private String SRC_DIR = "src";
@Getter
private String MAIN_DIR = "main";
@Getter
private String RESOURCES_DIR = "resources";
@Getter
private String BASE_DIR_PATH = System.getProperty("user.dir");
@Getter
// private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$";
private String PATH_DATA = "data";
@Getter
@Setter
private LwM2mModelProvider modelProvider;
@Getter
@Value("${transport.lwm2m.timeout:}")
private Long timeout;
@Getter
@Value("${transport.sessions.report_timeout}")
private long sessionReportTimeout;
@Getter
@Value("${transport.lwm2m.recommended_ciphers:}")
private boolean recommendedCiphers;
@Getter
@Value("${transport.lwm2m.recommended_supported_groups:}")
private boolean recommendedSupportedGroups;
@Getter
@Value("${transport.lwm2m.response_pool_size:}")
private int responsePoolSize;
@Getter
@Value("${transport.lwm2m.registered_pool_size:}")
private int registeredPoolSize;
@Getter
@Value("${transport.lwm2m.update_registered_pool_size:}")
private int updateRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.un_registered_pool_size:}")
private int unRegisteredPoolSize;
@Getter
@Value("${transport.lwm2m.secure.key_store_type:}")
private String keyStoreType;
@Getter
@Value("${transport.lwm2m.secure.key_store_path_file:}")
private String keyStorePathFile;
@Getter
@Setter
private KeyStore keyStoreValue;
@Getter
@Value("${transport.lwm2m.secure.key_store_password:}")
private String keyStorePasswordServer;
@Getter
@Value("${transport.lwm2m.secure.root_alias:}")
private String rootAlias;
@Getter
@Value("${transport.lwm2m.secure.enable_gen_new_key_psk_rpk:}")
private Boolean enableGenNewKeyPskRpk;
@Getter
@Value("${transport.lwm2m.server.id:}")
private Integer serverId;
@Getter
@Value("${transport.lwm2m.server.bind_address:}")
private String serverHost;
@Getter
@Value("${transport.lwm2m.server.secure.bind_address_security:}")
private String serverHostSecurity;
@Getter
@Value("${transport.lwm2m.server.bind_port_no_sec:}")
private Integer serverPortNoSec;
@Getter
@Value("${transport.lwm2m.server.secure.bind_port_security:}")
private Integer serverPortSecurity;
@Getter
@Value("${transport.lwm2m.server.secure.public_x:}")
private String serverPublicX;
@Getter
@Value("${transport.lwm2m.server.secure.public_y:}")
private String serverPublicY;
@Getter
@Value("${transport.lwm2m.server.secure.private_encoded:}")
private String serverPrivateEncoded;
@Getter
@Value("${transport.lwm2m.server.secure.alias:}")
private String serverAlias;
@PostConstruct
public void init() {
this.getInKeyStore();
}
private KeyStore getInKeyStore() {
try {
if (keyStoreValue != null && keyStoreValue.size() > 0)
return keyStoreValue;
} catch (KeyStoreException e) {
log.error("Uninitialized keystore [{}]", keyStoreValue.toString());
}
Path keyStorePath = (keyStorePathFile != null && !keyStorePathFile.isEmpty()) ? Paths.get(keyStorePathFile) :
(new File(Paths.get(getBaseDirPath(), PATH_DATA, KEY_STORE_DEFAULT_RESOURCE_PATH, KEY_STORE_DEFAULT_FILE).toUri()).isFile()) ?
Paths.get(getBaseDirPath(), PATH_DATA, KEY_STORE_DEFAULT_RESOURCE_PATH, KEY_STORE_DEFAULT_FILE) :
Paths.get(getBaseDirPath(), APP_DIR, TRANSPORT_DIR, LWM2M_DIR, SRC_DIR, MAIN_DIR, RESOURCES_DIR, KEY_STORE_DEFAULT_RESOURCE_PATH, KEY_STORE_DEFAULT_FILE);
File keyStoreFile = new File(keyStorePath.toUri());
if (keyStoreFile.isFile()) {
try {
InputStream inKeyStore = new FileInputStream(keyStoreFile);
keyStoreValue = KeyStore.getInstance(keyStoreType);
keyStoreValue.load(inKeyStore, keyStorePasswordServer == null ? null : keyStorePasswordServer.toCharArray());
} catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException e) {
log.error("[{}] Unable to load KeyStore files server, folder is not a directory", e.getMessage());
keyStoreValue = null;
}
log.info("[{}] Load KeyStore files server, folder is a directory", keyStoreFile.getAbsoluteFile());
} else {
log.error("[{}] Unable to load KeyStore files server, is not a file", keyStoreFile.getAbsoluteFile());
keyStoreValue = null;
}
return keyStoreValue;
}
private String getBaseDirPath() {
Path FULL_FILE_PATH;
if (BASE_DIR_PATH.endsWith("bin")) {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("bin$", ""));
} else if (BASE_DIR_PATH.endsWith("conf")) {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("conf$", ""));
} else if (BASE_DIR_PATH.endsWith("application")) {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.substring(0, BASE_DIR_PATH.length() - "application".length()));
} else {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH);
}
return FULL_FILE_PATH.toUri().getPath();
}
}

0
transport/lwm2m/src/main/data/credentials/serverKeyStore.jks → transport/lwm2m/src/main/data/lwm2mserver.jks

70
ui-ngx/src/app/core/services/mobile.service.ts

@ -0,0 +1,70 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Inject, Injectable } from '@angular/core';
import { WINDOW } from '@core/services/window.service';
import { isDefined } from '@core/utils';
import { MobileActionResult, WidgetMobileActionResult, WidgetMobileActionType } from '@shared/models/widget.models';
import { from, of } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { catchError } from 'rxjs/operators';
const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler';
const mobileHandler = 'tbMobileHandler';
// @dynamic
@Injectable({
providedIn: 'root'
})
export class MobileService {
private readonly mobileApp;
private readonly mobileChannel;
constructor(@Inject(WINDOW) private window: Window) {
const w = (this.window as any);
this.mobileChannel = w.flutter_inappwebview;
this.mobileApp = isDefined(this.mobileChannel);
}
public isMobileApp(): boolean {
return this.mobileApp;
}
public handleDashboardStateName(name: string) {
if (this.mobileApp) {
this.mobileChannel.callHandler(dashboardStateNameHandler, name);
}
}
public handleWidgetMobileAction<T extends MobileActionResult>(type: WidgetMobileActionType, ...args: any[]):
Observable<WidgetMobileActionResult<T>> {
if (this.mobileApp) {
return from(
this.mobileChannel.callHandler(mobileHandler, type, ...args) as Promise<WidgetMobileActionResult<T>>).pipe(
catchError((err: Error) => {
return of({
hasError: true,
error: err?.message ? err.message : `Failed to execute mobile action ${type}`
} as WidgetMobileActionResult<any>);
})
);
} else {
return of(null);
}
}
}

6
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-dashboard-page mat-content" style="padding-top: 150px;" <div class="tb-dashboard-page mat-content" [ngClass]="{'mobile-app': isMobileApp && !isEdit}" style="padding-top: 150px;"
fxFlex tb-fullscreen [fullscreen]="widgetEditMode || iframeMode || forceFullscreen || isFullscreen"> fxFlex tb-fullscreen [fullscreen]="widgetEditMode || iframeMode || forceFullscreen || isFullscreen">
<tb-hotkeys-cheatsheet #cheatSheetComponent></tb-hotkeys-cheatsheet> <tb-hotkeys-cheatsheet #cheatSheetComponent></tb-hotkeys-cheatsheet>
<section class="tb-dashboard-toolbar" <section class="tb-dashboard-toolbar"
@ -25,7 +25,7 @@
[toolbarOpened]="toolbarOpened" (triggerClick)="openToolbar()"> [toolbarOpened]="toolbarOpened" (triggerClick)="openToolbar()">
<div class="tb-dashboard-action-panels" fxLayout="column" fxLayout.gt-sm="row" <div class="tb-dashboard-action-panels" fxLayout="column" fxLayout.gt-sm="row"
fxLayoutAlign="center stretch" fxLayoutAlign.gt-sm="space-between center"> fxLayoutAlign="center stretch" fxLayoutAlign.gt-sm="space-between center">
<div class="tb-dashboard-action-panel" fxFlex="auto" fxLayout="row-reverse" <div class="tb-dashboard-action-panel" fxFlex="auto" fxLayout="row-reverse" [fxHide]="isMobileApp && !isEdit"
fxLayoutAlign.gt-sm="end center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> fxLayoutAlign.gt-sm="end center" fxLayoutAlign="space-between center" fxLayoutGap="12px">
<tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true"> <tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true">
</tb-user-menu> </tb-user-menu>
@ -49,7 +49,7 @@
<tb-states-component fxFlex.lt-md <tb-states-component fxFlex.lt-md
[statesControllerId]="isEdit ? 'default' : dashboardConfiguration.settings.stateControllerId" [statesControllerId]="isEdit ? 'default' : dashboardConfiguration.settings.stateControllerId"
[dashboardCtrl]="this" [dashboardCtrl]="this"
[dashboardId]="(!embedded && dashboard.id) ? dashboard.id.id : ''" [dashboardId]="setStateDashboardId ? dashboard.id.id : ''"
[isMobile]="isMobile" [isMobile]="isMobile"
[state]="dashboardCtx.state" [state]="dashboardCtx.state"
[currentState]="currentState" [currentState]="currentState"

9
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss

@ -18,6 +18,7 @@
$toolbar-height: 50px !default; $toolbar-height: 50px !default;
$fullscreen-toolbar-height: 64px !default; $fullscreen-toolbar-height: 64px !default;
$mobile-toolbar-height: 84px !default; $mobile-toolbar-height: 84px !default;
$mobile-app-toolbar-height: 40px !default;
tb-dashboard-page { tb-dashboard-page {
display: flex; display: flex;
@ -101,6 +102,14 @@ div.tb-dashboard-page {
} }
} }
&.mobile-app {
.tb-dashboard-container {
&.tb-dashboard-toolbar-opened {
margin-top: $mobile-app-toolbar-height;
}
}
}
mat-drawer-container.tb-dashboard-drawer-container { mat-drawer-container.tb-dashboard-drawer-container {
mat-drawer-container.tb-dashboard-layouts { mat-drawer-container.tb-dashboard-layouts {
width: 100%; width: 100%;

23
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -120,7 +120,8 @@ import {
DisplayWidgetTypesPanelData DisplayWidgetTypesPanelData
} from '@home/components/dashboard-page/widget-types-panel.component'; } from '@home/components/dashboard-page/widget-types-panel.component';
import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component'; import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component';
import {AliasEntityType, EntityType} from "@shared/models/entity-type.models"; import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
import { MobileService } from '@core/services/mobile.service';
// @dynamic // @dynamic
@Component({ @Component({
@ -159,6 +160,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
singlePageMode: boolean; singlePageMode: boolean;
forceFullscreen = this.authState.forceFullscreen; forceFullscreen = this.authState.forceFullscreen;
isMobileApp = this.mobileService.isMobileApp();
isFullscreen = false; isFullscreen = false;
isEdit = false; isEdit = false;
isEditingWidget = false; isEditingWidget = false;
@ -189,6 +191,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
currentCustomerId: string; currentCustomerId: string;
currentDashboardScope: DashboardPageScope; currentDashboardScope: DashboardPageScope;
setStateDashboardId = false;
addingLayoutCtx: DashboardPageLayoutContext; addingLayoutCtx: DashboardPageLayoutContext;
logo = 'assets/logo_title_white.svg'; logo = 'assets/logo_title_white.svg';
@ -284,6 +288,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private dashboardService: DashboardService, private dashboardService: DashboardService,
private itembuffer: ItemBufferService, private itembuffer: ItemBufferService,
private importExport: ImportExportService, private importExport: ImportExportService,
private mobileService: MobileService,
private fb: FormBuilder, private fb: FormBuilder,
private dialog: MatDialog, private dialog: MatDialog,
private translate: TranslateService, private translate: TranslateService,
@ -322,6 +327,19 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.reset(); this.reset();
this.dashboard = data.dashboard;
if (!this.embedded && this.dashboard.id) {
this.setStateDashboardId = true;
}
if (this.route.snapshot.queryParamMap.has('hideToolbar')) {
this.hideToolbar = this.route.snapshot.queryParamMap.get('hideToolbar') === 'true';
}
if (this.route.snapshot.queryParamMap.has('embedded')) {
this.embedded = this.route.snapshot.queryParamMap.get('embedded') === 'true';
}
this.currentDashboardId = data.currentDashboardId; this.currentDashboardId = data.currentDashboardId;
if (this.route.snapshot.params.customerId) { if (this.route.snapshot.params.customerId) {
@ -332,7 +350,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.currentCustomerId = this.authUser.customerId; this.currentCustomerId = this.authUser.customerId;
} }
this.dashboard = data.dashboard;
this.dashboardConfiguration = this.dashboard.configuration; this.dashboardConfiguration = this.dashboard.configuration;
this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow;
this.layouts.main.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboardCtx); this.layouts.main.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboardCtx);
@ -388,6 +405,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.currentCustomerId = null; this.currentCustomerId = null;
this.currentDashboardScope = null; this.currentDashboardScope = null;
this.setStateDashboardId = false;
this.dashboardCtx.state = null; this.dashboardCtx.state = null;
} }

36
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss

@ -21,6 +21,42 @@ $mobile-toolbar-height: 80px !default;
$half-mobile-toolbar-height: 40px !default; $half-mobile-toolbar-height: 40px !default;
$mobile-toolbar-height-total: 84px !default; $mobile-toolbar-height-total: 84px !default;
$mobile-app-toolbar-height: 40px !default;
div.tb-dashboard-page.mobile-app {
tb-dashboard-toolbar {
mat-fab-toolbar {
.mat-fab-toolbar-wrapper {
height: $mobile-app-toolbar-height;
mat-toolbar {
height: $mobile-app-toolbar-height;
min-height: $mobile-app-toolbar-height;
.mat-toolbar-tools {
height: $mobile-app-toolbar-height;
min-height: $mobile-app-toolbar-height;
}
mat-fab-actions {
height: $mobile-app-toolbar-height;
max-height: $mobile-app-toolbar-height;
.mat-fab-action-item {
height: $mobile-app-toolbar-height;
.tb-dashboard-action-panels {
height: $mobile-app-toolbar-height;
.tb-dashboard-action-panel {
height: $mobile-app-toolbar-height;
> div {
height: $mobile-app-toolbar-height;
}
}
}
}
}
}
}
}
}
}
tb-dashboard-toolbar { tb-dashboard-toolbar {
mat-fab-toolbar { mat-fab-toolbar {
mat-fab-trigger { mat-fab-trigger {

5
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts

@ -26,6 +26,7 @@ import { UtilsService } from '@core/services/utils.service';
import { base64toObj, objToBase64URI } from '@app/core/utils'; import { base64toObj, objToBase64URI } from '@app/core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { MobileService } from '@core/services/mobile.service';
@Component({ @Component({
selector: 'tb-default-state-controller', selector: 'tb-default-state-controller',
@ -40,6 +41,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
protected statesControllerService: StatesControllerService, protected statesControllerService: StatesControllerService,
private utils: UtilsService, private utils: UtilsService,
private entityService: EntityService, private entityService: EntityService,
private mobileService: MobileService,
private dashboardUtils: DashboardUtilsService) { private dashboardUtils: DashboardUtilsService) {
super(router, route, ngZone, statesControllerService); super(router, route, ngZone, statesControllerService);
} }
@ -229,6 +231,9 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) { private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
if (this.dashboardCtrl.dashboardCtx.state !== stateId) { if (this.dashboardCtrl.dashboardCtx.state !== stateId) {
this.dashboardCtrl.openDashboardState(stateId, openRightLayout); this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
if (stateId && this.statesValue[stateId]) {
this.mobileService.handleDashboardStateName(this.getStateName(stateId, this.statesValue[stateId]));
}
if (update) { if (update) {
this.updateLocation(); this.updateLocation();
} }

3
ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts

@ -28,6 +28,7 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { MobileService } from '@core/services/mobile.service';
@Component({ @Component({
selector: 'tb-entity-state-controller', selector: 'tb-entity-state-controller',
@ -44,6 +45,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
protected statesControllerService: StatesControllerService, protected statesControllerService: StatesControllerService,
private utils: UtilsService, private utils: UtilsService,
private entityService: EntityService, private entityService: EntityService,
private mobileService: MobileService,
private dashboardUtils: DashboardUtilsService) { private dashboardUtils: DashboardUtilsService) {
super(router, route, ngZone, statesControllerService); super(router, route, ngZone, statesControllerService);
} }
@ -270,6 +272,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) { private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
this.dashboardCtrl.openDashboardState(stateId, openRightLayout); this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
this.mobileService.handleDashboardStateName(this.getStateName(this.stateObject.length - 1));
if (update) { if (update) {
this.updateLocation(); this.updateLocation();
} }

3
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -55,6 +55,7 @@ import { ManageWidgetActionsComponent } from '@home/components/widget/action/man
import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component'; import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component';
import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component'; import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component';
import { CustomActionPrettyEditorComponent } from '@home/components/widget/action/custom-action-pretty-editor.component'; import { CustomActionPrettyEditorComponent } from '@home/components/widget/action/custom-action-pretty-editor.component';
import { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { CustomDialogContainerComponent } from '@home/components/widget/dialog/custom-dialog-container.component'; import { CustomDialogContainerComponent } from '@home/components/widget/dialog/custom-dialog-container.component';
import { ImportExportService } from '@home/components/import-export/import-export.service'; import { ImportExportService } from '@home/components/import-export/import-export.service';
@ -185,6 +186,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent, CustomActionPrettyEditorComponent,
MobileActionEditorComponent,
CustomDialogContainerComponent, CustomDialogContainerComponent,
ImportDialogComponent, ImportDialogComponent,
ImportDialogCsvComponent, ImportDialogCsvComponent,
@ -300,6 +302,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent, CustomActionPrettyEditorComponent,
MobileActionEditorComponent,
CustomDialogContainerComponent, CustomDialogContainerComponent,
ImportDialogComponent, ImportDialogComponent,
ImportDialogCsvComponent, ImportDialogCsvComponent,

100
ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.html

@ -0,0 +1,100 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div [formGroup]="mobileActionFormGroup">
<mat-form-field class="mat-block">
<mat-label translate>widget-action.mobile.action-type</mat-label>
<mat-select required formControlName="type">
<mat-option *ngFor="let actionType of mobileActionTypes" [value]="actionType">
{{ mobileActionTypeTranslations.get(mobileActionType[actionType]) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="mobileActionFormGroup.get('type').hasError('required')">
{{ 'widget-action.mobile.action-type-required' | translate }}
</mat-error>
</mat-form-field>
<div [formGroup]="mobileActionTypeFormGroup" [ngSwitch]="mobileActionFormGroup.get('type').value">
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.mapDirection ||
mobileActionFormGroup.get('type').value === mobileActionType.mapLocation ?
mobileActionFormGroup.get('type').value : ''">
<tb-js-func
formControlName="getLocationFunction"
functionName="getLocation"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</ng-template>
<ng-template [ngSwitchCase]="mobileActionType.makePhoneCall">
<tb-js-func
formControlName="getPhoneNumberFunction"
functionName="getPhoneNumber"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</ng-template>
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.takePhoto ||
mobileActionFormGroup.get('type').value === mobileActionType.takePictureFromGallery ||
mobileActionFormGroup.get('type').value === mobileActionType.takeScreenshot ?
mobileActionFormGroup.get('type').value : ''">
<tb-js-func
formControlName="processImageFunction"
functionName="processImage"
[functionArgs]="['imageUrl', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</ng-template>
<ng-template [ngSwitchCase]="mobileActionType.scanQrCode">
<tb-js-func
formControlName="processQrCodeFunction"
functionName="processQrCode"
[functionArgs]="['code', 'format', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</ng-template>
<ng-template [ngSwitchCase]="mobileActionType.getLocation">
<tb-js-func
formControlName="processLocationFunction"
functionName="processLocation"
[functionArgs]="['latitude', 'longitude', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</ng-template>
<ng-template [ngSwitchCase]="mobileActionFormGroup.get('type').value === mobileActionType.mapDirection ||
mobileActionFormGroup.get('type').value === mobileActionType.mapLocation ||
mobileActionFormGroup.get('type').value === mobileActionType.makePhoneCall ?
mobileActionFormGroup.get('type').value : ''">
<tb-js-func
formControlName="processLaunchResultFunction"
functionName="processLaunchResult"
[functionArgs]="['launched', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</ng-template>
</div>
<tb-js-func *ngIf="mobileActionFormGroup.get('type').value"
formControlName="handleEmptyResultFunction"
functionName="handleEmptyResult"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
<tb-js-func *ngIf="mobileActionFormGroup.get('type').value"
formControlName="handleErrorFunction"
functionName="handleError"
[functionArgs]="['error', '$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[editorCompleter]="customActionEditorCompleter"
></tb-js-func>
</div>

253
ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.ts

@ -0,0 +1,253 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { deepClone } from '@core/utils';
import {
WidgetMobileActionDescriptor,
WidgetMobileActionType, widgetMobileActionTypeTranslationMap
} from '@shared/models/widget.models';
import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { JsFuncComponent } from '@shared/components/js-func.component';
import {
getDefaultGetLocationFunction, getDefaultGetPhoneNumberFunction,
getDefaultHandleEmptyResultFunction,
getDefaultHandleErrorFunction,
getDefaultProcessImageFunction,
getDefaultProcessLaunchResultFunction, getDefaultProcessLocationFunction,
getDefaultProcessQrCodeFunction
} from '@home/components/widget/action/mobile-action-editor.models';
@Component({
selector: 'tb-mobile-action-editor',
templateUrl: './mobile-action-editor.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MobileActionEditorComponent),
multi: true
}]
})
export class MobileActionEditorComponent implements ControlValueAccessor, OnInit {
@ViewChildren(JsFuncComponent) jsFuncComponents: QueryList<JsFuncComponent>;
mobileActionTypes = Object.keys(WidgetMobileActionType);
mobileActionTypeTranslations = widgetMobileActionTypeTranslationMap;
mobileActionType = WidgetMobileActionType;
customActionEditorCompleter = CustomActionEditorCompleter;
mobileActionFormGroup: FormGroup;
mobileActionTypeFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.mobileActionFormGroup = this.fb.group({
type: [null, Validators.required],
handleEmptyResultFunction: [null],
handleErrorFunction: [null]
});
this.mobileActionFormGroup.get('type').valueChanges.subscribe((type: WidgetMobileActionType) => {
let action: WidgetMobileActionDescriptor = this.mobileActionFormGroup.value;
if (this.mobileActionTypeFormGroup) {
action = {...action, ...this.mobileActionTypeFormGroup.value};
}
this.updateMobileActionType(type, action);
});
this.mobileActionFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.mobileActionFormGroup.disable({emitEvent: false});
if (this.mobileActionTypeFormGroup) {
this.mobileActionTypeFormGroup.disable({emitEvent: false});
}
} else {
this.mobileActionFormGroup.enable({emitEvent: false});
if (this.mobileActionTypeFormGroup) {
this.mobileActionTypeFormGroup.enable({emitEvent: false});
}
}
}
writeValue(value: WidgetMobileActionDescriptor | null): void {
this.mobileActionFormGroup.patchValue({type: value?.type,
handleEmptyResultFunction: value?.handleEmptyResultFunction,
handleErrorFunction: value?.handleErrorFunction}, {emitEvent: false});
this.updateMobileActionType(value?.type, value);
}
private updateModel() {
let descriptor: WidgetMobileActionDescriptor = null;
if (this.mobileActionFormGroup.valid && this.mobileActionTypeFormGroup.valid) {
descriptor = { ...this.mobileActionFormGroup.getRawValue(), ...this.mobileActionTypeFormGroup.getRawValue() };
}
this.propagateChange(descriptor);
}
private updateMobileActionType(type?: WidgetMobileActionType, action?: WidgetMobileActionDescriptor) {
const prevType = action?.type;
const targetType = type || prevType;
const changed = prevType !== type;
if (changed && targetType) {
let handleEmptyResultFunction = action?.handleEmptyResultFunction;
const defaultHandleEmptyResultFunction = getDefaultHandleEmptyResultFunction(targetType);
if (defaultHandleEmptyResultFunction !== handleEmptyResultFunction) {
handleEmptyResultFunction = getDefaultHandleEmptyResultFunction(type);
this.mobileActionFormGroup.patchValue({handleEmptyResultFunction}, {emitEvent: false});
}
let handleErrorFunction = action?.handleErrorFunction;
const defaultHandleErrorFunction = getDefaultHandleErrorFunction(targetType);
if (defaultHandleErrorFunction !== handleErrorFunction) {
handleErrorFunction = getDefaultHandleErrorFunction(type);
this.mobileActionFormGroup.patchValue({handleErrorFunction}, {emitEvent: false});
}
}
this.mobileActionTypeFormGroup = this.fb.group({});
if (type) {
switch (type) {
case WidgetMobileActionType.takePictureFromGallery:
case WidgetMobileActionType.takePhoto:
case WidgetMobileActionType.takeScreenshot:
let processImageFunction = action?.processImageFunction;
if (changed) {
const defaultProcessImageFunction = getDefaultProcessImageFunction(targetType);
if (defaultProcessImageFunction !== processImageFunction) {
processImageFunction = getDefaultProcessImageFunction(type);
}
}
this.mobileActionTypeFormGroup.addControl(
'processImageFunction',
this.fb.control(processImageFunction, [])
);
break;
case WidgetMobileActionType.mapDirection:
case WidgetMobileActionType.mapLocation:
let getLocationFunction = action?.getLocationFunction;
let processLaunchResultFunction = action?.processLaunchResultFunction;
if (changed) {
const defaultGetLocationFunction = getDefaultGetLocationFunction();
if (defaultGetLocationFunction !== getLocationFunction) {
getLocationFunction = defaultGetLocationFunction;
}
const defaultProcessLaunchResultFunction = getDefaultProcessLaunchResultFunction(targetType);
if (defaultProcessLaunchResultFunction !== processLaunchResultFunction) {
processLaunchResultFunction = getDefaultProcessLaunchResultFunction(type);
}
}
this.mobileActionTypeFormGroup.addControl(
'getLocationFunction',
this.fb.control(getLocationFunction, [Validators.required])
);
this.mobileActionTypeFormGroup.addControl(
'processLaunchResultFunction',
this.fb.control(processLaunchResultFunction, [])
);
break;
case WidgetMobileActionType.scanQrCode:
let processQrCodeFunction = action?.processQrCodeFunction;
if (changed) {
const defaultProcessQrCodeFunction = getDefaultProcessQrCodeFunction();
if (defaultProcessQrCodeFunction !== processQrCodeFunction) {
processQrCodeFunction = defaultProcessQrCodeFunction;
}
}
this.mobileActionTypeFormGroup.addControl(
'processQrCodeFunction',
this.fb.control(processQrCodeFunction, [])
);
break;
case WidgetMobileActionType.makePhoneCall:
let getPhoneNumberFunction = action?.getPhoneNumberFunction;
processLaunchResultFunction = action?.processLaunchResultFunction;
if (changed) {
const defaultGetPhoneNumberFunction = getDefaultGetPhoneNumberFunction();
if (defaultGetPhoneNumberFunction !== getPhoneNumberFunction) {
getPhoneNumberFunction = defaultGetPhoneNumberFunction;
}
const defaultProcessLaunchResultFunction = getDefaultProcessLaunchResultFunction(targetType);
if (defaultProcessLaunchResultFunction !== processLaunchResultFunction) {
processLaunchResultFunction = getDefaultProcessLaunchResultFunction(type);
}
}
this.mobileActionTypeFormGroup.addControl(
'getPhoneNumberFunction',
this.fb.control(getPhoneNumberFunction, [Validators.required])
);
this.mobileActionTypeFormGroup.addControl(
'processLaunchResultFunction',
this.fb.control(processLaunchResultFunction, [])
);
break;
case WidgetMobileActionType.getLocation:
let processLocationFunction = action?.processLocationFunction;
if (changed) {
const defaultProcessLocationFunction = getDefaultProcessLocationFunction();
if (defaultProcessLocationFunction !== processLocationFunction) {
processLocationFunction = defaultProcessLocationFunction;
}
}
this.mobileActionTypeFormGroup.addControl(
'processLocationFunction',
this.fb.control(processLocationFunction, [Validators.required])
);
break;
}
}
this.mobileActionTypeFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
public validateOnSubmit() {
for (const jsFuncComponent of this.jsFuncComponents.toArray()) {
jsFuncComponent.validateOnSubmit();
}
}
}

317
ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.models.ts

@ -0,0 +1,317 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { WidgetMobileActionType } from '@shared/models/widget.models';
const processImageFunctionTemplate =
'// Function body to process image obtained as a result of mobile action (take photo, take image from gallery, etc.). \n' +
'// - imageUrl - image URL in base64 data format\n\n' +
'showImageDialog(\'--TITLE--\', imageUrl);\n' +
'//saveEntityImageAttribute(\'image\', imageUrl);\n' +
'\n' +
'function showImageDialog(title, imageUrl) {\n' +
' setTimeout(function() {\n' +
' widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe();\n' +
' }, 100);\n' +
'}\n' +
'\n' +
'function saveEntityImageAttribute(attributeName, imageUrl) {\n' +
' if (entityId) {\n' +
' let attributes = [{\n' +
' key: attributeName, value: imageUrl\n' +
' }];\n' +
' widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe(\n' +
' function() {\n' +
' widgetContext.showSuccessToast(\'Image attribute saved!\');\n' +
' },\n' +
' function(error) {\n' +
' widgetContext.dialogs.alert(\'Image attribute save failed\', JSON.stringify(error));\n' +
' }\n' +
' );\n' +
' }\n' +
'}\n' +
'\n' +
'var\n' +
' imageDialogTemplate =\n' +
' \'<div aria-label="Image">\' +\n' +
' \'<form #theForm="ngForm">\' +\n' +
' \'<mat-toolbar fxLayout="row" color="primary">\' +\n' +
' \'<h2>{{title}}</h2>\' +\n' +
' \'<span fxFlex></span>\' +\n' +
' \'<button mat-icon-button (click)="close()">\' +\n' +
' \'<mat-icon>close</mat-icon>\' +\n' +
' \'</button>\' +\n' +
' \'</mat-toolbar>\' +\n' +
' \'<div mat-dialog-content>\' +\n' +
' \'<div class="mat-content mat-padding">\' +\n' +
' \'<div fxLayout="column" fxFlex>\' +\n' +
' \'<div style="padding-top: 20px;">\' +\n' +
' \'<img [src]="imageUrl" style="height: 300px;"/>\' +\n' +
' \'</div>\' +\n' +
' \'</div>\' +\n' +
' \'</div>\' +\n' +
' \'</div>\' +\n' +
' \'<div mat-dialog-actions fxLayout="row">\' +\n' +
' \'<span fxFlex></span>\' +\n' +
' \'<button mat-button (click)="close()" style="margin-right:20px;">Close</button>\' +\n' +
' \'</div>\' +\n' +
' \'</form>\' +\n' +
' \'</div>\';\n' +
'\n' +
'function ImageDialogController(instance) {\n' +
' let vm = instance;\n' +
' vm.title = vm.data.title;\n' +
' vm.imageUrl = vm.data.imageUrl;\n' +
' vm.close = function ()\n' +
' {\n' +
' vm.dialogRef.close(null);\n' +
' }\n' +
'}\n';
const processLaunchResultFunctionTemplate =
'// Optional function body to process result of attempt to launch external mobile application (for ex. map application or phone call application). \n' +
'// - launched - boolean value indicating if the external application was successfully launched.\n\n' +
'showLaunchStatusDialog(\'--TITLE--\', launched);\n' +
'\n' +
'function showLaunchStatusDialog(title, status) {\n' +
' setTimeout(function() {\n' +
' widgetContext.dialogs.alert(title, status ? \'Successfully launched\' : \'Failed to launch\').subscribe();\n' +
' }, 100);\n' +
'}\n';
const processQrCodeFunction =
'// Function body to process result of QR code scanning. \n' +
'// - code - scanned QR code\n' +
'// - format - scanned QR code format\n\n' +
'showQrCodeDialog(\'QR Code\', code, format);\n' +
'\n' +
'function showQrCodeDialog(title, code, format) {\n' +
' setTimeout(function() {\n' +
' widgetContext.dialogs.alert(title, \'Code: [\'+code+\']<br>Format: \' + format).subscribe();\n' +
' }, 100);\n' +
'}\n';
const processLocationFunction =
'// Function body to process current location of the phone. \n' +
'// - latitude - phone location latitude\n' +
'// - longitude - phone location longitude\n\n' +
'showLocationDialog(\'Location\', latitude, longitude);\n' +
'// saveEntityLocationAttributes(\'latitude\', \'longitude\', latitude, longitude);\n' +
'\n' +
'function showImageDialog(title, imageUrl) {\n' +
' setTimeout(function() {\n' +
' widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe();\n' +
' }, 100);\n' +
'}\n' +
'\n' +
'function saveEntityLocationAttributes(latitudeAttributeName, longitudeAttributeName, latitude, longitude) {\n' +
' if (entityId) {\n' +
' let attributes = [\n' +
' { key: latitudeAttributeName, value: latitude },\n' +
' { key: longitudeAttributeName, value: longitude }\n' +
' ];\n' +
' widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe(\n' +
' function() {\n' +
' widgetContext.showSuccessToast(\'Location attributes saved!\');\n' +
' },\n' +
' function(error) {\n' +
' widgetContext.dialogs.alert(\'Location attributes save failed\', JSON.stringify(error));\n' +
' }\n' +
' );\n' +
' }\n' +
'}\n' +
'\n' +
'\n' +
'function showLocationDialog(title, latitude, longitude) {\n' +
' setTimeout(function() {\n' +
' widgetContext.dialogs.alert(title, \'Latitude: \'+latitude+\'<br>Longitude: \' + longitude).subscribe();\n' +
' }, 100);\n' +
'}';
const handleEmptyResultFunctionTemplate =
'// Optional function body to handle empty result. \n' +
'// Usually this happens when user cancels the action (for ex. by pressing phone back button). \n\n' +
'showEmptyResultDialog(\'--MESSAGE--\');\n' +
'\n' +
'function showEmptyResultDialog(message) {\n' +
' setTimeout(function() {\n' +
' widgetContext.dialogs.alert(\'Empty result\', message).subscribe();\n' +
' }, 100);\n' +
'}\n';
const handleErrorFunctionTemplate =
'// Optional function body to handle error occurred while mobile action execution \n' +
'// - error - Error message\n\n' +
'showErrorDialog(\'--TITLE--\', error);\n' +
'\n' +
'function showErrorDialog(title, error) {\n' +
' setTimeout(function() {\n' +
' widgetContext.dialogs.alert(title, error).subscribe();\n' +
' }, 100);\n' +
'}\n';
const getLocationFunctionTemplate =
'// Function body that should return location as array of two numbers (latitude, longitude) for further processing by mobile action.\n' +
'// Usually location can be obtained from entity attributes/telemetry. \n\n' +
'return getLocationFromEntityAttributes();\n' +
'//return [30, 30]; // TEST LOCATION\n' +
'\n' +
'\n' +
'function getLocationFromEntityAttributes() {\n' +
' if (entityId) {\n' +
' return widgetContext.attributeService.getEntityAttributes(entityId, \'SERVER_SCOPE\', [\'latitude\', \'longitude\']).pipe(widgetContext.rxjs.map(function(attributeData) {\n' +
' var res = [0,0];\n' +
' if (attributeData && attributeData.length === 2) {\n' +
' res[0] = attributeData.filter(function (data) { return data.key === \'latitude\'})[0].value;\n' +
' res[1] = attributeData.filter(function (data) { return data.key === \'longitude\'})[0].value;\n' +
' }\n' +
' return res;\n' +
' }));\n' +
' } else {\n' +
' return [0,0];\n' +
' }\n' +
'}\n';
const getPhoneNumberFunctionTemplate =
'// Function body that should return phone number for further processing by mobile action.\n' +
'// Usually phone number can be obtained from entity attributes/telemetry. \n\n' +
'return getPhoneNumberFromEntityAttributes();\n' +
'//return 123456789; // TEST PHONE NUMBER\n' +
'\n' +
'\n' +
'function getPhoneNumberFromEntityAttributes() {\n' +
' if (entityId) {\n' +
' return widgetContext.attributeService.getEntityAttributes(entityId, \'SERVER_SCOPE\', [\'phone\']).pipe(widgetContext.rxjs.map(function(attributeData) {\n' +
' var res = 0;\n' +
' if (attributeData && attributeData.length === 1) {\n' +
' res = attributeData[0].value;\n' +
' }\n' +
' return res;\n' +
' }));\n' +
' } else {\n' +
' return 0;\n' +
' }\n' +
'}\n';
export function getDefaultProcessImageFunction(type: WidgetMobileActionType): string {
let title;
switch (type) {
case WidgetMobileActionType.takePictureFromGallery:
title = 'Gallery picture';
break;
case WidgetMobileActionType.takePhoto:
title = 'Photo';
break;
case WidgetMobileActionType.takeScreenshot:
title = 'Screenshot';
break;
}
return processImageFunctionTemplate.replace('--TITLE--', title);
}
export function getDefaultProcessLaunchResultFunction(type: WidgetMobileActionType): string {
let title;
switch (type) {
case WidgetMobileActionType.mapLocation:
title = 'Map location';
break;
case WidgetMobileActionType.mapDirection:
title = 'Map direction';
break;
case WidgetMobileActionType.makePhoneCall:
title = 'Phone call';
break;
}
return processLaunchResultFunctionTemplate.replace('--TITLE--', title);
}
export function getDefaultProcessQrCodeFunction() {
return processQrCodeFunction;
}
export function getDefaultProcessLocationFunction() {
return processLocationFunction;
}
export function getDefaultGetLocationFunction() {
return getLocationFunctionTemplate;
}
export function getDefaultGetPhoneNumberFunction() {
return getPhoneNumberFunctionTemplate;
}
export function getDefaultHandleEmptyResultFunction(type: WidgetMobileActionType): string {
let message = 'Mobile action was cancelled!';
switch (type) {
case WidgetMobileActionType.takePictureFromGallery:
message = 'Take picture from gallery action was cancelled!';
break;
case WidgetMobileActionType.takePhoto:
message = 'Take photo action was cancelled!';
break;
case WidgetMobileActionType.mapDirection:
message = 'Open map directions was not invoked!';
break;
case WidgetMobileActionType.mapLocation:
message = 'Open location on map was not invoked!';
break;
case WidgetMobileActionType.scanQrCode:
message = 'Scan QR code action was canceled!';
break;
case WidgetMobileActionType.makePhoneCall:
message = 'Phone call was not invoked!';
break;
case WidgetMobileActionType.getLocation:
message = 'Get location action was canceled!';
break;
case WidgetMobileActionType.takeScreenshot:
message = 'Take screenshot action was cancelled!';
break;
}
return handleEmptyResultFunctionTemplate.replace('--MESSAGE--', message);
}
export function getDefaultHandleErrorFunction(type: WidgetMobileActionType): string {
let title = 'Mobile action failed';
switch (type) {
case WidgetMobileActionType.takePictureFromGallery:
title = 'Failed to take picture from gallery';
break;
case WidgetMobileActionType.takePhoto:
title = 'Failed to take photo';
break;
case WidgetMobileActionType.mapDirection:
title = 'Failed to open map directions';
break;
case WidgetMobileActionType.mapLocation:
title = 'Failed to open map location';
break;
case WidgetMobileActionType.scanQrCode:
title = 'Failed to scan QR code';
break;
case WidgetMobileActionType.makePhoneCall:
title = 'Failed to make phone call';
break;
case WidgetMobileActionType.getLocation:
title = 'Failed to get phone location';
break;
case WidgetMobileActionType.takeScreenshot:
title = 'Failed to take screenshot';
break;
}
return handleErrorFunctionTemplate.replace('--TITLE--', title);
}

5
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html

@ -180,6 +180,11 @@
formControlName="customAction"> formControlName="customAction">
</tb-custom-action-pretty-editor> </tb-custom-action-pretty-editor>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="widgetActionType.mobileAction">
<tb-mobile-action-editor #mobileActionEditor
formControlName="mobileAction">
</tb-mobile-action-editor>
</ng-template>
</section> </section>
</fieldset> </fieldset>
</div> </div>

30
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts

@ -45,6 +45,7 @@ import { Dashboard } from '@shared/models/dashboard.models';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component';
export interface WidgetActionDialogData { export interface WidgetActionDialogData {
isAdd: boolean; isAdd: boolean;
@ -64,6 +65,8 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
@ViewChild('dashboardStateInput') dashboardStateInput: ElementRef; @ViewChild('dashboardStateInput') dashboardStateInput: ElementRef;
@ViewChild('mobileActionEditor', {static: false}) mobileActionEditor: MobileActionEditorComponent;
widgetActionFormGroup: FormGroup; widgetActionFormGroup: FormGroup;
actionTypeFormGroup: FormGroup; actionTypeFormGroup: FormGroup;
@ -197,6 +200,12 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
this.fb.control(toCustomAction(action), [Validators.required]) this.fb.control(toCustomAction(action), [Validators.required])
); );
break; break;
case WidgetActionType.mobileAction:
this.actionTypeFormGroup.addControl(
'mobileAction',
this.fb.control(action ? action.mobileAction : null, [Validators.required])
);
break;
} }
} }
} }
@ -313,14 +322,19 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
save(): void { save(): void {
this.submitted = true; this.submitted = true;
const type: WidgetActionType = this.widgetActionFormGroup.get('type').value; if (this.mobileActionEditor != null) {
let result: WidgetActionDescriptorInfo; this.mobileActionEditor.validateOnSubmit();
if (type === WidgetActionType.customPretty) { }
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.get('customAction').value}; if (this.widgetActionFormGroup.valid && this.actionTypeFormGroup.valid) {
} else { const type: WidgetActionType = this.widgetActionFormGroup.get('type').value;
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.value}; let result: WidgetActionDescriptorInfo;
if (type === WidgetActionType.customPretty) {
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.get('customAction').value};
} else {
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.value};
}
result.id = this.action.id;
this.dialogRef.close(result);
} }
result.id = this.action.id;
this.dialogRef.close(result);
} }
} }

182
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -39,12 +39,12 @@ import {
defaultLegendConfig, defaultLegendConfig,
LegendConfig, LegendConfig,
LegendData, LegendData,
LegendPosition, LegendPosition, MobileActionResult,
Widget, Widget,
WidgetActionDescriptor, WidgetActionDescriptor,
widgetActionSources, widgetActionSources,
WidgetActionType, WidgetActionType,
WidgetComparisonSettings, WidgetComparisonSettings, WidgetMobileActionDescriptor, WidgetMobileActionType,
WidgetResource, WidgetResource,
widgetType, widgetType,
WidgetTypeParameters WidgetTypeParameters
@ -77,7 +77,7 @@ import { EntityId } from '@shared/models/id/entity-id';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { ResourcesService } from '@core/services/resources.service'; import { ResourcesService } from '@core/services/resources.service';
import { catchError, switchMap } from 'rxjs/operators'; import { catchError, map, switchMap } from 'rxjs/operators';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { DeviceService } from '@app/core/http/device.service'; import { DeviceService } from '@app/core/http/device.service';
@ -97,6 +97,8 @@ import { AlarmDataService } from '@core/api/alarm-data.service';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ComponentType } from '@angular/cdk/portal'; import { ComponentType } from '@angular/cdk/portal';
import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token';
import { MobileService } from '@core/services/mobile.service';
import { DialogService } from '@core/services/dialog.service';
@Component({ @Component({
selector: 'tb-widget', selector: 'tb-widget',
@ -177,6 +179,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
private alarmDataService: AlarmDataService, private alarmDataService: AlarmDataService,
private translate: TranslateService, private translate: TranslateService,
private utils: UtilsService, private utils: UtilsService,
private mobileService: MobileService,
private dialogs: DialogService,
private raf: RafService, private raf: RafService,
private ngZone: NgZone, private ngZone: NgZone,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {
@ -1094,6 +1098,178 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} }
); );
break; break;
case WidgetActionType.mobileAction:
const mobileAction = descriptor.mobileAction;
this.handleMobileAction($event, mobileAction, entityId, entityName, additionalParams, entityLabel);
break;
}
}
private handleMobileAction($event: Event, mobileAction: WidgetMobileActionDescriptor,
entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) {
const type = mobileAction.type;
let argsObservable: Observable<any[]>;
switch (type) {
case WidgetMobileActionType.takePictureFromGallery:
case WidgetMobileActionType.takePhoto:
case WidgetMobileActionType.scanQrCode:
case WidgetMobileActionType.getLocation:
case WidgetMobileActionType.takeScreenshot:
argsObservable = of([]);
break;
case WidgetMobileActionType.mapDirection:
case WidgetMobileActionType.mapLocation:
const getLocationFunctionString = mobileAction.getLocationFunction;
const getLocationFunction = new Function('$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', getLocationFunctionString);
const locationArgs = getLocationFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
if (locationArgs && locationArgs instanceof Observable) {
argsObservable = locationArgs;
} else {
argsObservable = of(locationArgs);
}
argsObservable = argsObservable.pipe(map(latLng => {
let valid = false;
if (Array.isArray(latLng) && latLng.length === 2) {
if (typeof latLng[0] === 'number' && typeof latLng[1] === 'number') {
valid = true;
}
}
if (valid) {
return latLng;
} else {
throw new Error('Location function did not return valid array of latitude/longitude!');
}
}));
break;
case WidgetMobileActionType.makePhoneCall:
const getPhoneNumberFunctionString = mobileAction.getPhoneNumberFunction;
const getPhoneNumberFunction = new Function('$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', getPhoneNumberFunctionString);
const phoneNumberArg = getPhoneNumberFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
if (phoneNumberArg && phoneNumberArg instanceof Observable) {
argsObservable = phoneNumberArg.pipe(map(phoneNumber => [phoneNumber]));
} else {
argsObservable = of([phoneNumberArg]);
}
argsObservable = argsObservable.pipe(map(phoneNumberArr => {
let valid = false;
if (Array.isArray(phoneNumberArr) && phoneNumberArr.length === 1) {
if (phoneNumberArr[0] !== null) {
valid = true;
}
}
if (valid) {
return phoneNumberArr;
} else {
throw new Error('Phone number function did not return valid number!');
}
}));
break;
}
argsObservable.subscribe((args) => {
this.mobileService.handleWidgetMobileAction(type, ...args).subscribe(
(result) => {
if (result) {
if (result.hasError) {
this.handleWidgetMobileActionError(result.error, $event, mobileAction, entityId, entityName, additionalParams, entityLabel);
} else if (result.hasResult) {
const actionResult = result.result;
switch (type) {
case WidgetMobileActionType.takePictureFromGallery:
case WidgetMobileActionType.takePhoto:
case WidgetMobileActionType.takeScreenshot:
const imageUrl = actionResult.imageUrl;
if (mobileAction.processImageFunction && mobileAction.processImageFunction.length) {
try {
const processImageFunction = new Function('imageUrl', '$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', mobileAction.processImageFunction);
processImageFunction(imageUrl, $event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
}
break;
case WidgetMobileActionType.scanQrCode:
const code = actionResult.code;
const format = actionResult.format;
if (mobileAction.processQrCodeFunction && mobileAction.processQrCodeFunction.length) {
try {
const processQrCodeFunction = new Function('code', 'format', '$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', mobileAction.processQrCodeFunction);
processQrCodeFunction(code, format, $event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
}
break;
case WidgetMobileActionType.getLocation:
const latitude = actionResult.latitude;
const longitude = actionResult.longitude;
if (mobileAction.processLocationFunction && mobileAction.processLocationFunction.length) {
try {
const processLocationFunction = new Function('latitude', 'longitude', '$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', mobileAction.processLocationFunction);
processLocationFunction(latitude, longitude, $event, this.widgetContext,
entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
}
break;
case WidgetMobileActionType.mapDirection:
case WidgetMobileActionType.mapLocation:
case WidgetMobileActionType.makePhoneCall:
const launched = actionResult.launched;
if (mobileAction.processLaunchResultFunction && mobileAction.processLaunchResultFunction.length) {
try {
const processLaunchResultFunction = new Function('launched', '$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', mobileAction.processLaunchResultFunction);
processLaunchResultFunction(launched, $event, this.widgetContext,
entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
}
break;
}
} else {
if (mobileAction.handleEmptyResultFunction && mobileAction.handleEmptyResultFunction.length) {
try {
const handleEmptyResultFunction = new Function('$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', mobileAction.handleEmptyResultFunction);
handleEmptyResultFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
}
}
}
}
);
},
(err) => {
let errorMessage;
if (err && typeof err === 'string') {
errorMessage = err;
} else if (err && err.message) {
errorMessage = err.message;
}
errorMessage = `Failed to get mobile action arguments${errorMessage ? `: ${errorMessage}` : '!'}`;
this.handleWidgetMobileActionError(errorMessage, $event, mobileAction, entityId, entityName, additionalParams, entityLabel);
});
}
private handleWidgetMobileActionError(error: string, $event: Event, mobileAction: WidgetMobileActionDescriptor,
entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) {
if (mobileAction.handleErrorFunction && mobileAction.handleErrorFunction.length) {
try {
const handleErrorFunction = new Function('error', '$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', 'entityLabel', mobileAction.handleErrorFunction);
handleErrorFunction(error, $event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
} }
} }

5
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -77,6 +77,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { SortOrder } from '@shared/models/page/sort-order'; import { SortOrder } from '@shared/models/page/sort-order';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { map, mergeMap } from 'rxjs/operators';
export interface IWidgetAction { export interface IWidgetAction {
name: string; name: string;
@ -238,7 +239,9 @@ export class WidgetContext {
rxjs = { rxjs = {
forkJoin, forkJoin,
of of,
map,
mergeMap
}; };
showSuccessToast(message: string, duration: number = 1000, showSuccessToast(message: string, duration: number = 1000,

97
ui-ngx/src/app/shared/models/widget.models.ts

@ -332,7 +332,19 @@ export enum WidgetActionType {
updateDashboardState = 'updateDashboardState', updateDashboardState = 'updateDashboardState',
openDashboard = 'openDashboard', openDashboard = 'openDashboard',
custom = 'custom', custom = 'custom',
customPretty = 'customPretty' customPretty = 'customPretty',
mobileAction = 'mobileAction'
}
export enum WidgetMobileActionType {
takePictureFromGallery = 'takePictureFromGallery',
takePhoto = 'takePhoto',
mapDirection = 'mapDirection',
mapLocation = 'mapLocation',
scanQrCode = 'scanQrCode',
makePhoneCall = 'makePhoneCall',
getLocation = 'getLocation',
takeScreenshot = 'takeScreenshot'
} }
export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>( export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>(
@ -341,10 +353,90 @@ export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>(
[ WidgetActionType.updateDashboardState, 'widget-action.update-dashboard-state' ], [ WidgetActionType.updateDashboardState, 'widget-action.update-dashboard-state' ],
[ WidgetActionType.openDashboard, 'widget-action.open-dashboard' ], [ WidgetActionType.openDashboard, 'widget-action.open-dashboard' ],
[ WidgetActionType.custom, 'widget-action.custom' ], [ WidgetActionType.custom, 'widget-action.custom' ],
[ WidgetActionType.customPretty, 'widget-action.custom-pretty' ] [ WidgetActionType.customPretty, 'widget-action.custom-pretty' ],
[ WidgetActionType.mobileAction, 'widget-action.mobile-action' ]
]
);
export const widgetMobileActionTypeTranslationMap = new Map<WidgetMobileActionType, string>(
[
[ WidgetMobileActionType.takePictureFromGallery, 'widget-action.mobile.take-picture-from-gallery' ],
[ WidgetMobileActionType.takePhoto, 'widget-action.mobile.take-photo' ],
[ WidgetMobileActionType.mapDirection, 'widget-action.mobile.map-direction' ],
[ WidgetMobileActionType.mapLocation, 'widget-action.mobile.map-location' ],
[ WidgetMobileActionType.scanQrCode, 'widget-action.mobile.scan-qr-code' ],
[ WidgetMobileActionType.makePhoneCall, 'widget-action.mobile.make-phone-call' ],
[ WidgetMobileActionType.getLocation, 'widget-action.mobile.get-location' ],
[ WidgetMobileActionType.takeScreenshot, 'widget-action.mobile.take-screenshot' ]
] ]
); );
export interface MobileLaunchResult {
launched: boolean;
}
export interface MobileImageResult {
imageUrl: string;
}
export interface MobileQrCodeResult {
code: string;
format: string;
}
export interface MobileLocationResult {
latitude: number;
longitude: number;
}
export type MobileActionResult = MobileLaunchResult &
MobileImageResult &
MobileQrCodeResult &
MobileLocationResult;
export interface WidgetMobileActionResult<T extends MobileActionResult> {
result?: T;
hasResult: boolean;
error?: string;
hasError: boolean;
}
export interface ProcessImageDescriptor {
processImageFunction: string;
}
export interface ProcessLaunchResultDescriptor {
processLaunchResultFunction?: string;
}
export interface LaunchMapDescriptor extends ProcessLaunchResultDescriptor {
getLocationFunction: string;
}
export interface ScanQrCodeDescriptor {
processQrCodeFunction: string;
}
export interface MakePhoneCallDescriptor extends ProcessLaunchResultDescriptor {
getPhoneNumberFunction: string;
}
export interface GetLocationDescriptor {
processLocationFunction: string;
}
export type WidgetMobileActionDescriptors = ProcessImageDescriptor &
LaunchMapDescriptor &
ScanQrCodeDescriptor &
MakePhoneCallDescriptor &
GetLocationDescriptor;
export interface WidgetMobileActionDescriptor extends WidgetMobileActionDescriptors {
type: WidgetMobileActionType;
handleErrorFunction?: string;
handleEmptyResultFunction?: string;
}
export interface CustomActionDescriptor { export interface CustomActionDescriptor {
customFunction?: string; customFunction?: string;
customResources?: Array<WidgetResource>; customResources?: Array<WidgetResource>;
@ -369,6 +461,7 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
dialogHeight?: number; dialogHeight?: number;
setEntityId?: boolean; setEntityId?: boolean;
stateEntityParamName?: string; stateEntityParamName?: string;
mobileAction?: WidgetMobileActionDescriptor;
} }
export interface WidgetComparisonSettings { export interface WidgetComparisonSettings {

15
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2721,6 +2721,7 @@
"open-dashboard": "Navigate to other dashboard", "open-dashboard": "Navigate to other dashboard",
"custom": "Custom action", "custom": "Custom action",
"custom-pretty": "Custom action (with HTML template)", "custom-pretty": "Custom action (with HTML template)",
"mobile-action": "Mobile action",
"target-dashboard-state": "Target dashboard state", "target-dashboard-state": "Target dashboard state",
"target-dashboard-state-required": "Target dashboard state is required", "target-dashboard-state-required": "Target dashboard state is required",
"set-entity-from-widget": "Set entity from widget", "set-entity-from-widget": "Set entity from widget",
@ -2732,7 +2733,19 @@
"dialog-width": "Dialog width in percents relative to viewport width", "dialog-width": "Dialog width in percents relative to viewport width",
"dialog-height": "Dialog height in percents relative to viewport height", "dialog-height": "Dialog height in percents relative to viewport height",
"dialog-size-range-error": "Dialog size percent value should be in a range from 1 to 100.", "dialog-size-range-error": "Dialog size percent value should be in a range from 1 to 100.",
"open-new-browser-tab": "Open in a new browser tab" "open-new-browser-tab": "Open in a new browser tab",
"mobile": {
"action-type": "Mobile action type",
"action-type-required": "Mobile action type is required",
"take-picture-from-gallery": "Take picture from gallery",
"take-photo": "Take photo",
"map-direction": "Open map directions",
"map-location": "Open map location",
"scan-qr-code": "Scan QR Code",
"make-phone-call": "Make phone call",
"get-location": "Get phone location",
"take-screenshot": "Take screenshot"
}
}, },
"widgets-bundle": { "widgets-bundle": {
"current": "Current bundle", "current": "Current bundle",

Loading…
Cancel
Save