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.rpc.EdgeGrpcService;
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.queue.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService;
@ -267,7 +267,7 @@ public abstract class BaseController {
protected TbDeviceProfileCache deviceProfileCache;
@Autowired
protected LwM2MModelsRepository lwM2MModelsRepository;
protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository;
@Autowired(required = false)
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')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
@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) {
try {
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)
@ResponseBody
public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode,
@PathVariable("bootstrapServerIs") boolean bootstrapServerIs) throws ThingsboardException {
@PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException {
try {
return lwM2MModelsRepository.getBootstrapSecurityInfo(securityMode, bootstrapServerIs);
return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer);
} catch (Exception 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;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
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 java.math.BigInteger;
@ -42,96 +43,59 @@ import java.security.spec.KeySpec;
@Slf4j
@Service
@RequiredArgsConstructor
@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 ";
@Autowired
LwM2MTransportConfigServer contextServer;
@Autowired
LwM2MTransportConfigBootstrap contextBootStrap;
private final LwM2MTransportServerConfig serverConfig;
private final LwM2MTransportBootstrapConfig bootstrapConfig;
/**
* @param securityMode
* @param bootstrapServerIs
* @param bootstrapServer
* @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());
return getBootstrapServer(bootstrapServerIs, lwM2MSecurityMode);
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode);
result.setBootstrapServerIs(bootstrapServer);
return result;
}
/**
* @param bootstrapServerIs
* @param mode
* @return ServerSecurityConfig more value is default: Important - port, host, publicKey
*/
private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) {
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) {
ServerSecurityConfig bsServ = new ServerSecurityConfig();
bsServ.setBootstrapServerIs(bootstrapServerIs);
if (bootstrapServerIs) {
bsServ.setServerId(contextBootStrap.getBootstrapServerId());
switch (mode) {
case NO_SEC:
bsServ.setHost(contextBootStrap.getBootstrapHost());
bsServ.setPort(contextBootStrap.getBootstrapPortNoSec());
bsServ.setServerPublicKey("");
break;
case PSK:
bsServ.setHost(contextBootStrap.getBootstrapHostSecurity());
bsServ.setPort(contextBootStrap.getBootstrapPortSecurity());
bsServ.setServerPublicKey("");
break;
case RPK:
case X509:
bsServ.setHost(contextBootStrap.getBootstrapHostSecurity());
bsServ.setPort(contextBootStrap.getBootstrapPortSecurity());
bsServ.setServerPublicKey(getPublicKey (contextBootStrap.getBootstrapAlias(), this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY()));
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;
}
bsServ.setServerId(serverConfig.getId());
switch (mode) {
case NO_SEC:
bsServ.setHost(serverConfig.getHost());
bsServ.setPort(serverConfig.getPort());
bsServ.setServerPublicKey("");
break;
case PSK:
bsServ.setHost(serverConfig.getSecureHost());
bsServ.setPort(serverConfig.getSecurePort());
bsServ.setServerPublicKey("");
break;
case RPK:
case X509:
bsServ.setHost(serverConfig.getSecureHost());
bsServ.setPort(serverConfig.getSecurePort());
bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY()));
break;
default:
break;
}
return bsServ;
}
private String getPublicKey (String alias, String publicServerX, String publicServerY) {
private String getPublicKey(String alias, String publicServerX, String publicServerY) {
String publicKey = getServerPublicKeyX509(alias);
return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY);
}
/**
* @param alias
* @return PublicKey format HexString or null
*/
private String getServerPublicKeyX509(String alias) {
try {
X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias);
X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(alias);
return Hex.encodeHexString(serverCertificate.getEncoded());
} catch (CertificateEncodingException | KeyStoreException e) {
e.printStackTrace();
@ -139,11 +103,6 @@ public class LwM2MModelsRepository {
return null;
}
/**
* @param publicServerX
* @param publicServerY
* @return PublicKey format HexString or null
*/
private String getRPKPublicKey(String publicServerX, String publicServerY) {
try {
/** Get Elliptic Curve Parameter spec for secp256r1 */

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

@ -630,38 +630,14 @@ transport:
lwm2m:
# Enable/disable lvm2m transport protocol.
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:
id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC:5685}"
secure:
bind_address_security: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY:5686}"
# create_rpk: "${CREATE_RPK:}"
bind_port: "${LWM2M_BIND_PORT:5685}"
security:
bind_address: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_SECURITY:5686}"
# 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_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
@ -671,19 +647,34 @@ transport:
enable: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}"
secure:
bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
bind_port: "${LWM2M_BIND_PORT_BS:5687}"
security:
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
# 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_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
# Only Certificate_x509:
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
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
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.context.annotation.Bean;
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.LwM2MInMemoryBootstrapConfigStore;
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.security.AlgorithmParameters;
@ -66,10 +69,10 @@ public class LwM2MTransportBootstrapServerConfiguration {
private boolean pskMode = false;
@Autowired
private LwM2MTransportContextBootstrap contextBs;
private LwM2MTransportServerConfig serverConfig;
@Autowired
private LwM2mTransportContextServer contextS;
private LwM2MTransportContextBootstrap contextBs;
@Autowired
private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
@ -81,13 +84,13 @@ public class LwM2MTransportBootstrapServerConfiguration {
@Bean
public LeshanBootstrapServer getLeshanBootstrapServer() {
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) {
LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder();
builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPortNoSec);
builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapHostSecurity(), bootstrapSecurePort);
builder.setLocalAddress(this.contextBs.getCtxBootStrap().getHost(), bootstrapPortNoSec);
builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getSecureHost(), bootstrapSecurePort);
/** Create CoAP Config */
builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort));
@ -106,14 +109,13 @@ public class LwM2MTransportBootstrapServerConfiguration {
/** Create and Set DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getLwM2MTransportConfigServer().isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getLwM2MTransportConfigServer().isRecommendedCiphers());
dtlsConfig.setRecommendedSupportedGroupsOnly(serverConfig.isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(serverConfig.isRecommendedCiphers());
if (this.pskMode) {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256);
}
else {
} else {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
TLS_PSK_WITH_AES_128_CBC_SHA256,
@ -133,10 +135,10 @@ public class LwM2MTransportBootstrapServerConfiguration {
private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) {
try {
if (this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue() != null) {
KeyStore keyStoreServer = this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue();
if (serverConfig.getKeyStoreValue() != null) {
KeyStore keyStoreServer = serverConfig.getKeyStoreValue();
if (this.setBuilderX509(builder)) {
X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getLwM2MTransportConfigServer().getRootAlias());
X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(serverConfig.getRootCertificateAlias());
if (rootCAX509Cert != null) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
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
*/
try {
X509Certificate serverCertificate = (X509Certificate) this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias());
PrivateKey privateKey = (PrivateKey) this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getLwM2MTransportConfigServer().getKeyStorePasswordServer() == null ? null : this.contextS.getLwM2MTransportConfigServer().getKeyStorePasswordServer().toCharArray());
X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getCertificateAlias());
PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getCertificateAlias(), serverConfig.getKeyStorePassword() == null ? null : serverConfig.getKeyStorePassword().toCharArray());
PublicKey publicKey = serverCertificate.getPublicKey();
if (serverCertificate != null &&
privateKey != null && privateKey.getEncoded().length > 0 &&
publicKey != null && publicKey.getEncoded().length > 0) {
if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) {
builder.setPublicKey(serverCertificate.getPublicKey());
builder.setPrivateKey(privateKey);
builder.setCertificateChain(new X509Certificate[]{serverCertificate});
@ -199,10 +199,12 @@ public class LwM2MTransportBootstrapServerConfiguration {
}
private void infoPramsUri(String mode) {
log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]",
log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]",
mode,
this.contextBs.getCtxBootStrap().getBootstrapHost() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(),
this.contextBs.getCtxBootStrap().getBootstrapHostSecurity() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortSecurity());
this.contextBs.getCtxBootStrap().getHost(),
this.contextBs.getCtxBootStrap().getPort(),
this.contextBs.getCtxBootStrap().getSecureHost(),
this.contextBs.getCtxBootStrap().getSecurePort());
}
@ -236,23 +238,25 @@ public class LwM2MTransportBootstrapServerConfiguration {
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
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 */
byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray());
byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray());
/** Create key specs */
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
/** Get public key */
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 */
byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().toCharArray());
byte[] privateS = Hex.decodeHex(privateEncodedKey.toCharArray());
try {
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS));
} 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.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
@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'")
public class LwM2MTransportContextBootstrap extends TransportContext {
private final LwM2MTransportConfigBootstrap lwM2MTransportConfigBootstrap;
private final LwM2MTransportBootstrapConfig lwM2MTransportBootstrapConfig;
public LwM2MTransportContextBootstrap(LwM2MTransportConfigBootstrap ctxBootStrap) {
this.lwM2MTransportConfigBootstrap = ctxBootStrap;
public LwM2MTransportContextBootstrap(LwM2MTransportBootstrapConfig ctxBootStrap) {
this.lwM2MTransportBootstrapConfig = ctxBootStrap;
}
public LwM2MTransportConfigBootstrap getCtxBootStrap() {
return this.lwM2MTransportConfigBootstrap;
public LwM2MTransportBootstrapConfig getCtxBootStrap() {
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.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer;
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.security.GeneralSecurityException;
@ -43,12 +44,12 @@ import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.BOOTSTRAP_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVERS;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getBootstrapParametersFromThingsboard;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.BOOTSTRAP_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVERS;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getBootstrapParametersFromThingsboard;
@Slf4j
@Service("LwM2MBootstrapSecurityStore")
@ -59,17 +60,19 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
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.lwM2MCredentialsSecurityInfoValidator = lwM2MCredentialsSecurityInfoValidator;
this.context = context;
this.helper = helper;
}
@Override
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) {
/** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
@ -93,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
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) {
/** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
@ -158,19 +161,19 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class);
LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class);
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));
if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
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());
context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo);
helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo);
return lwM2MBootstrapConfig;
} else {
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());
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;
}
}

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
* limitations under the License.
*/
package org.thingsboard.server.common.transport.lwm2m;
package org.thingsboard.server.transport.lwm2m.config;
import lombok.Getter;
import lombok.Setter;
@ -29,53 +29,42 @@ import java.util.Map;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MTransportConfigBootstrap {
@Getter
@Value("${transport.lwm2m.bootstrap.enable:}")
private Boolean bootstrapEnable;
public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig {
@Getter
@Value("${transport.lwm2m.bootstrap.id:}")
private Integer bootstrapServerId;
private Integer id;
@Getter
@Value("${transport.lwm2m.bootstrap.bind_address:}")
private String bootstrapHost;
private String host;
@Getter
@Value("${transport.lwm2m.bootstrap.bind_port_no_sec:}")
private Integer bootstrapPortNoSec;
@Value("${transport.lwm2m.bootstrap.bind_port:}")
private Integer port;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_address_security:}")
private String bootstrapHostSecurity;
@Value("${transport.lwm2m.bootstrap.security.bind_address:}")
private String secureHost;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.bind_port_security:}")
private Integer bootstrapPortSecurity;
@Value("${transport.lwm2m.bootstrap.security.bind_port:}")
private Integer securePort;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.public_x:}")
private String bootstrapPublicX;
@Value("${transport.lwm2m.bootstrap.security.public_x:}")
private String publicX;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.public_y:}")
private String bootstrapPublicY;
@Value("${transport.lwm2m.bootstrap.security.public_y:}")
private String publicY;
@Getter
@Setter
private PublicKey bootstrapPublicKey;
@Value("${transport.lwm2m.bootstrap.security.private_encoded:}")
private String privateEncoded;
@Getter
@Value("${transport.lwm2m.bootstrap.secure.private_encoded:}")
private String bootstrapPrivateEncoded;
@Value("${transport.lwm2m.bootstrap.security.alias:}")
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;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
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.ValidateDeviceLwM2MCredentialsRequestMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
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.security.GeneralSecurityException;
@ -44,13 +47,11 @@ import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X5
@Slf4j
@Component
@TbLwM2mTransportComponent
@RequiredArgsConstructor
public class LwM2mCredentialsSecurityInfoValidator {
private final LwM2mTransportContextServer contextS;
public LwM2mCredentialsSecurityInfoValidator(LwM2mTransportContextServer contextS) {
this.contextS = contextS;
}
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
/**
* Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials
@ -58,17 +59,17 @@ public class LwM2mCredentialsSecurityInfoValidator {
* @param keyValue -
* @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);
final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1];
contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(),
context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(),
new TransportServiceCallback<>() {
@Override
public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) {
String credentialsBody = msg.getCredentialsBody();
resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue);
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));
latch.countDown();
}
@ -81,7 +82,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
}
});
try {
latch.await(contextS.getLwM2MTransportConfigServer().getTimeout(), TimeUnit.MILLISECONDS);
latch.await(config.getTimeout(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("Failed to await credentials!", e);
}
@ -95,9 +96,9 @@ public class LwM2mCredentialsSecurityInfoValidator {
* @param keyValue -
* @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();
JsonObject objectMsg = LwM2mTransportHandler.validateJson(jsonStr);
JsonObject objectMsg = LwM2mTransportHandlerUtil.validateJson(jsonStr);
if (objectMsg != null && !objectMsg.isJsonNull()) {
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;
endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk;
if (object != null && !object.isJsonNull()) {
if (keyValue.equals(LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP)) {
if (keyValue.equals(LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP)) {
result.setBootstrapJsonCredential(object);
result.setEndPoint(endPoint);
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.SessionInfoProto;
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.LwM2mClientContext;
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.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.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.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.LwM2mTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertJsonArrayToSet;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_OBJECT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVICE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertJsonArrayToSet;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getAckCallback;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validateObjectVerFromKey;
@Slf4j
@Service
@TbLwM2mTransportComponent
public class LwM2mTransportServiceImpl implements LwM2mTransportService {
public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler {
private ExecutorService executorRegistered;
private ExecutorService executorUpdateRegistered;
private ExecutorService executorUnRegistered;
private LwM2mValueConverterImpl converter;
private FirmwareDataCache firmwareDataCache;
private final TransportService transportService;
public final LwM2mTransportContextServer lwM2mTransportContextServer;
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final FirmwareDataCache firmwareDataCache;
private final LwM2mTransportServerHelper helper;
private final LwM2mClientContext lwM2mClientContext;
private final LeshanServer leshanServer;
private final LwM2mTransportRequest lwM2mTransportRequest;
public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer,
LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer,
@Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache) {
public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
LwM2mClientContext lwM2mClientContext,
@Lazy LwM2mTransportRequest lwM2mTransportRequest,
FirmwareDataCache firmwareDataCache,
LwM2mTransportContext context) {
this.transportService = transportService;
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
this.config = config;
this.helper = helper;
this.lwM2mClientContext = lwM2mClientContext;
this.leshanServer = leshanServer;
this.lwM2mTransportRequest = lwM2mTransportRequest;
this.firmwareDataCache = firmwareDataCache;
this.context = context;
}
@PostConstruct
public void init() {
this.lwM2mTransportContextServer.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) lwM2mTransportContextServer.getLwM2MTransportConfigServer().getSessionReportTimeout()), lwM2mTransportContextServer.getLwM2MTransportConfigServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS);
this.executorRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getRegisteredPoolSize(),
this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS);
this.executorRegistered = Executors.newFixedThreadPool(this.config.getRegisteredPoolSize(),
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)));
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)));
this.converter = LwM2mValueConverterImpl.getInstance();
}
@ -278,10 +279,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override
public void setCancelObservations(Registration registration) {
if (registration != null) {
Set<Observation> observations = leshanServer.getObservationService().getObservations(registration);
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration,
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 -> {
String pathName = tsKvProto.getKv().getKey();
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())) {
this.getInfoFirmwareUpdate(lwM2MClient);
}
if (pathIdVer != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config
.getModelProvider());
if (resourceModel != null && resourceModel.operations.isWritable()) {
this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer);
@ -359,7 +361,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} else if (msg.getSharedDeletedCount() > 0) {
msg.getSharedUpdatedList().forEach(tsKvProto -> {
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())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew);
}
@ -403,7 +405,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override
public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
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
public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
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
@ -428,7 +430,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} else {
lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), lwm2mClientRpcRequest.getContentFormatName(),
lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(),
this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), lwm2mClientRpcRequest);
this.config.getTimeout(), lwm2mClientRpcRequest);
}
} catch (Exception e) {
if (lwm2mClientRpcRequest == null) {
@ -456,7 +458,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
lwm2mClientRpcRequest.setRequestId(toDeviceRequest.getRequestId());
lwm2mClientRpcRequest.setSessionInfo(sessionInfo);
lwm2mClientRpcRequest.setValidTypeOper(toDeviceRequest.getMethodName());
JsonObject rpcRequest = LwM2mTransportHandler.validateJson(toDeviceRequest.getParams());
JsonObject rpcRequest = LwM2mTransportHandlerUtil.validateJson(toDeviceRequest.getParams());
if (rpcRequest != null) {
if (rpcRequest.has(lwm2mClientRpcRequest.keyNameKey)) {
String targetIdVer = this.getPresentPathIntoProfile(sessionInfo,
@ -545,7 +547,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override
public void doTrigger(Registration registration, String path) {
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 -
*/
protected void onAwakeDev(Registration registration) {
@Override
public void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId());
//TODO: associate endpointId with device information.
@ -609,10 +612,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param logMsg - text msg
* @param registrationId - Id of Registration LwM2M Client
*/
@Override
public void sendLogsToThingsboard(String logMsg, String registrationId) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId);
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);
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
if (clientObjects != null && clientObjects.size() > 0) {
if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
if (LWM2M_STRATEGY_2 == LwM2mTransportHandlerUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
// #2
lwM2MClient.getPendingReadRequests().addAll(clientObjects);
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(),
null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null));
null, this.config.getTimeout(), null));
}
// #1
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects);
@ -685,7 +692,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config
.getModelProvider())) {
if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) &&
lwM2MClient.getFrUpdate().getCurrentFwVersion() != null
@ -724,10 +731,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (results != null && sessionInfo != null) {
if (results.getResultAttributes().size() > 0) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
}
if (results.getResultTelemetries().size() > 0) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo);
this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo);
}
}
} catch (Exception e) {
@ -776,7 +783,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
ConcurrentHashMap<String, Object> finalParams = params;
pathSend.forEach(target -> {
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)) {
lwM2MClient.initReadValue(this, null);
@ -861,7 +868,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null;
if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
Object valueKvProto = null;
if (resourceValue.isMultiInstances()) {
valueKvProto = new JsonObject();
@ -878,7 +885,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
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) {
log.error("Failed to add parameters.", e);
@ -897,7 +904,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) {
LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
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,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
}
@ -1090,10 +1097,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (pathIds.isResource()) {
if (READ.equals(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)) {
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()) {
ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew;
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.put(OBJECT_VERSION, "");
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 -> {
if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != 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()))) {
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE,
ContentFormat.TLV.getName(), valueNew,
this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null);
this.config.getTimeout(), null);
} else {
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",
@ -1267,7 +1274,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
// #2.1
lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> {
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;
} else {
return SessionInfoProto.newBuilder()
.setNodeId(this.lwM2mTransportContextServer.getNodeId())
.setNodeId(this.context.getNodeId())
.setSessionIdMSB(lwM2MClient.getSessionId().getMostSignificantBits())
.setSessionIdLSB(lwM2MClient.getSessionId().getLeastSignificantBits())
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
@ -1354,7 +1361,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
if (keyNamesMap.values().size() > 0) {
try {
//#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));
} catch (AdaptorException e) {
log.warn("Failed to decode get attributes request", e);
@ -1372,6 +1379,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setType(FirmwareType.FIRMWARE.name())
.build();
transportService.process(sessionInfo, getFirmwareRequestMsg,
new TransportServiceCallback<>() {
@ -1401,7 +1409,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
public void readRequestToClientFirmwareVer(Registration registration) {
String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration);
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 chunk = 0;
byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk);
Integer objectId = 5;
String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId);
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.TLV.getName(),
firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null);
String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(FR_OBJECT_ID);
String targetIdVer = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0;
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, config.getTimeout(), null);
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) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config
.getModelProvider());
Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathIdVer);
@ -1448,9 +1455,4 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
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;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.network.config.NetworkConfig;
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.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
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.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
@ -64,60 +69,81 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g
@Slf4j
@Component
@TbLwM2mTransportComponent
public class LwM2mTransportServerConfiguration {
@RequiredArgsConstructor
public class DefaultLwM2mTransportService implements LwM2MTransportService {
private PublicKey publicKey;
private PrivateKey privateKey;
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 EditableSecurityStore securityStore;
private final LwM2mClientContext lwM2mClientContext;
public LwM2mTransportServerConfiguration(LwM2mTransportContextServer context, CaliforniumRegistrationStore registrationStore, EditableSecurityStore securityStore, LwM2mClientContext lwM2mClientContext) {
this.context = context;
this.registrationStore = registrationStore;
this.securityStore = securityStore;
this.lwM2mClientContext = lwM2mClientContext;
private LeshanServer server;
@PostConstruct
public void init() {
if (config.getEnableGenNewKeyPskRpk()) {
new LWM2MGenerationPSkRPkECC();
}
this.server = getLhServer(config.getPort(), config.getSecurePort());
this.startLhServer();
this.context.setServer(server);
}
@Bean
public LeshanServer getLeshanServer() {
log.info("Starting LwM2M transport Server... PostConstruct");
return this.getLhServer(this.context.getLwM2MTransportConfigServer().getServerPortNoSec(), this.context.getLwM2MTransportConfigServer().getServerPortSecurity());
private void startLhServer() {
log.info("Starting LwM2M transport Server...");
this.server.start();
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) {
LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(this.context.getLwM2MTransportConfigServer().getServerHost(), serverPortNoSec);
builder.setLocalSecureAddress(this.context.getLwM2MTransportConfigServer().getServerHostSecurity(), serverSecurePort);
builder.setLocalAddress(config.getHost(), serverPortNoSec);
builder.setLocalSecureAddress(config.getSecureHost(), serverSecurePort);
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()));
/** Create CoAP Config */
/* Create CoAP Config */
NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort);
BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig);
builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort));
/** Define model provider (Create Models )*/
LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.context);
this.context.getLwM2MTransportConfigServer().setModelProvider(modelProvider);
/* Define model provider (Create Models )*/
LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.helper, this.context);
config.setModelProvider(modelProvider);
builder.setObjectModelProvider(modelProvider);
/** Create credentials */
/* Create credentials */
this.setServerWithCredentials(builder);
/** Set securityStore with new registrationStore */
/* Set securityStore with new registrationStore */
builder.setSecurityStore(securityStore);
builder.setRegistrationStore(registrationStore);
/** Create DTLS Config */
/* Create DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setServerOnly(true);
dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportConfigServer().isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportConfigServer().isRecommendedCiphers());
dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups());
dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers());
if (this.pskMode) {
dtlsConfig.setSupportedCipherSuites(
TLS_PSK_WITH_AES_128_CCM_8,
@ -130,27 +156,27 @@ public class LwM2mTransportServerConfiguration {
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
}
/** Set DTLS Config */
/* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig);
/** Create LWM2M server */
/* Create LWM2M server */
return builder.build();
}
private void setServerWithCredentials(LeshanServerBuilder builder) {
try {
if (this.context.getLwM2MTransportConfigServer().getKeyStoreValue() != null) {
if (config.getKeyStoreValue() != null) {
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) {
X509Certificate[] trustedCertificates = new X509Certificate[1];
trustedCertificates[0] = rootCAX509Cert;
builder.setTrustedCertificates(trustedCertificates);
} else {
/** by default trust all */
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
/** Set securityStore with registrationStore*/
/* Set securityStore with registrationStore*/
builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() {
@Override
protected boolean matchX509Identity(String endpoint, String receivedX509CommonName,
@ -163,7 +189,7 @@ public class LwM2mTransportServerConfiguration {
this.infoPramsUri("RPK");
this.infoParamsServerKey(this.publicKey, this.privateKey);
} else {
/** by default trust all */
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for LWM2MServer");
this.pskMode = true;
@ -175,17 +201,11 @@ public class LwM2mTransportServerConfiguration {
}
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 {
X509Certificate serverCertificate = (X509Certificate) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportConfigServer().getServerAlias());
PrivateKey privateKey = (PrivateKey) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getKey(this.context.getLwM2MTransportConfigServer().getServerAlias(), this.context.getLwM2MTransportConfigServer().getKeyStorePasswordServer() == null ? null : this.context.getLwM2MTransportConfigServer().getKeyStorePasswordServer().toCharArray());
X509Certificate serverCertificate = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getCertificateAlias());
PrivateKey privateKey = (PrivateKey) config.getKeyStoreValue().getKey(config.getCertificateAlias(), config.getKeyStorePassword() == null ? null : config.getKeyStorePassword().toCharArray());
PublicKey publicKey = serverCertificate.getPublicKey();
if (serverCertificate != null &&
privateKey != null && privateKey.getEncoded().length > 0 &&
publicKey != null && publicKey.getEncoded().length > 0) {
if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) {
builder.setPublicKey(serverCertificate.getPublicKey());
builder.setPrivateKey(privateKey);
builder.setCertificateChain(new X509Certificate[]{serverCertificate});
@ -212,10 +232,12 @@ public class LwM2mTransportServerConfiguration {
}
private void infoPramsUri(String mode) {
log.info("Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]",
mode,
this.context.getLwM2MTransportConfigServer().getServerHost() + ":" + this.context.getLwM2MTransportConfigServer().getServerPortNoSec(),
this.context.getLwM2MTransportConfigServer().getServerHostSecurity() + ":" + this.context.getLwM2MTransportConfigServer().getServerPortSecurity());
LwM2MTransportServerConfig lwM2MTransportServerConfig = config;
log.info("Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode,
lwM2MTransportServerConfig.getHost(),
lwM2MTransportServerConfig.getPort(),
lwM2MTransportServerConfig.getSecureHost(),
lwM2MTransportServerConfig.getSecurePort());
}
private boolean setServerRPK(LeshanServerBuilder builder) {
@ -233,55 +255,42 @@ public class LwM2mTransportServerConfiguration {
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 {
/** Get Elliptic Curve Parameter spec for secp256r1 */
/* Get Elliptic Curve Parameter spec for secp256r1 */
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
algoParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
if (this.context.getLwM2MTransportConfigServer().getServerPublicX() != null &&
!this.context.getLwM2MTransportConfigServer().getServerPublicX().isEmpty() &&
this.context.getLwM2MTransportConfigServer().getServerPublicY() != null &&
!this.context.getLwM2MTransportConfigServer().getServerPublicY().isEmpty()) {
/** Get point values */
byte[] publicX = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPublicY().toCharArray());
/** Create key specs */
LwM2MTransportServerConfig serverConfig = config;
if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) {
byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray());
byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray());
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
parameterSpec);
/** Get keys */
this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
}
if (this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded() != null &&
!this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded().isEmpty()) {
/** Get private key */
byte[] privateS = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded().toCharArray());
String privateEncodedKey = serverConfig.getPrivateEncoded();
if (StringUtils.isNotEmpty(privateEncodedKey)) {
byte[] privateS = Hex.decodeHex(privateEncodedKey.toCharArray());
try {
this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS));
} 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) {
/** Get x coordinate */
/* Get x coordinate */
byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray();
if (x[0] == 0)
x = Arrays.copyOfRange(x, 1, x.length);
/** Get Y coordinate */
/* Get Y coordinate */
byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray();
if (y[0] == 0)
y = Arrays.copyOfRange(y, 1, y.length);
/** Get Curves params */
/* Get Curves params */
String params = ((ECPublicKey) publicKey).getParams().toString();
String privHex = Hex.encodeHexString(privateKey.getEncoded());
log.info(" \n- Public Key (Hex): [{}] \n" +
@ -298,4 +307,9 @@ public class LwM2mTransportServerConfiguration {
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();
coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec);
coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort);
/**
* Example:Property for large packet:
* #NetworkConfig config = new NetworkConfig();
* #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32);
* #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32);
* #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048);
* #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3);
* #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000);
/*
Example:Property for large packet:
#NetworkConfig config = new NetworkConfig();
#config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32);
#config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32);
#config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048);
#config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3);
#config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000);
*/
/**
* 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.
* - 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.
* CoAP client will try to use block mode
* or adapt the block size when receiving a 4.13 Entity too large response code
/*
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.
- 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.
CoAP client will try to use block mode
or adapt the block size when receiving a 4.13 Entity too large response code
*/
coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true);
/***
* 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.
* - 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.
/*
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.
- 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.
*/
coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true);
coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000);
/**
* !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13
* 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
* transparent> blockwise transfer.
* This option serves as a safeguard against excessive memory
* consumption when many resources contain large bodies that cannot be
* transferred in a single CoAP message. This option has no impact on
* *manually* managed blockwise transfers in which the blocks are handled individually.
* 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.
* 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.
/*
!!! REQUEST_ENTITY_TOO_LARGE CODE=4.13
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
transparent> blockwise transfer.
This option serves as a safeguard against excessive memory
consumption when many resources contain large bodies that cannot be
transferred in a single CoAP message. This option has no impact on
*manually* managed blockwise transfers in which the blocks are handled individually.
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.
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.
*/
// coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 8192);
coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 16384);
/**
* The default DTLS response matcher.
* Supported values are STRICT, RELAXED, or PRINCIPAL.
* The default value is STRICT.
* Create new instance of udp endpoint context matcher.
* Params:
* checkAddress
* – true with address check, (STRICT, UDP)
* - false, without
coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024);
/*
The default DTLS response matcher.
Supported values are STRICT, RELAXED, or PRINCIPAL.
The default value is STRICT.
Create new instance of udp endpoint context matcher.
Params:
checkAddress
– true with address check, (STRICT, UDP)
- false, without
*/
coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT");
/**
* https://tools.ietf.org/html/rfc7959#section-2.9.3
* 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
/*
https://tools.ietf.org/html/rfc7959#section-2.9.3
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
*/
coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 512);
/**
* The maximum payload size (in bytes) that can be transferred in a
* 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.
* In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol
* DEFAULT_VALUE = 1024
coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024);
/*
The maximum payload size (in bytes) that can be transferred in a
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.
In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol
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);

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 static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
@Slf4j
public class LwM2mServerListener {
private final LwM2mTransportServiceImpl service;
private final LwM2mTransportMsgHandler service;
public LwM2mServerListener(LwM2mTransportServiceImpl service) {
public LwM2mServerListener(LwM2mTransportMsgHandler 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
public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
private LwM2mTransportServiceImpl service;
private DefaultLwM2MTransportMsgHandler service;
private TransportProtos.SessionInfoProto sessionInfo;
public LwM2mSessionMsgListener(LwM2mTransportServiceImpl service, TransportProtos.SessionInfoProto sessionInfo) {
public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler service, TransportProtos.SessionInfoProto sessionInfo) {
this.service = service;
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;
@Slf4j
public class LwM2mTransportHandler {
public class LwM2mTransportHandlerUtil {
public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
@ -256,7 +256,7 @@ public class LwM2mTransportHandler {
ObjectMapper mapper = new ObjectMapper();
String profileStr = mapper.writeValueAsString(profile);
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) {
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.Optional;
public interface LwM2mTransportService extends TbTransportService {
public interface LwM2mTransportMsgHandler {
void onRegistered(Registration registration, Collection<Observation> previousObsersations);
@ -60,4 +60,8 @@ public interface LwM2mTransportService extends TbTransportService {
void doTrigger(Registration registration, String path);
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;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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.thingsboard.server.common.transport.TransportService;
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.LwM2mClientContext;
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.leshan.core.ResponseCode.BAD_REQUEST;
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.LwM2mTransportHandler.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.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.createWriteAttributeRequest;
@Slf4j
@Service
@TbLwM2mTransportComponent
@RequiredArgsConstructor
public class LwM2mTransportRequest {
private ExecutorService executorResponse;
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 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;
}
private final DefaultLwM2MTransportMsgHandler serviceImpl;
@PostConstruct
public void init() {
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)));
}
@ -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
* {@code ObservationService#cancelObservation()}
*/
leshanServer.getObservationService().cancelObservations(registration, target);
context.getServer().getObservationService().cancelObservations(registration, target);
break;
case EXECUTE:
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer()
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config
.getModelProvider());
if (params != null && !resourceModel.multiple) {
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds));
@ -171,7 +160,7 @@ public class LwM2mTransportRequest {
break;
case WRITE_REPLACE:
// 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());
if (contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(),
@ -232,7 +221,7 @@ public class LwM2mTransportRequest {
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
}
} 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());
String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO,
OBSERVE_READ_ALL.type, observationPaths);
@ -259,7 +248,7 @@ public class LwM2mTransportRequest {
@SuppressWarnings("unchecked")
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()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
@ -415,17 +404,40 @@ public class LwM2mTransportRequest {
}
}
private void infoWriteResponse(Registration registration, LwM2mResponse response,
DownlinkRequest request) {
LwM2mNode node = ((WriteRequest) request).getNode();
Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(),
((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath());
String msg = String.format("%s: Update finished successfully: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s",
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(),
response.getCode().getName(), request.getPath().toString(), value);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(),
request.getPath().toString(), value);
private void infoWriteResponse(Registration registration, LwM2mResponse response, DownlinkRequest request) {
try {
LwM2mNode node = ((WriteRequest) request).getNode();
String msg;
Object value;
LwM2mSingleResource singleResource = (LwM2mSingleResource) node;
if (singleResource.getType() == ResourceModel.Type.STRING || singleResource.getType() == ResourceModel.Type.OPAQUE) {
int valueLength;
if (singleResource.getType() == ResourceModel.Type.STRING) {
valueLength = ((String) singleResource.getValue()).length();
value = ((String) singleResource.getValue())
.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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
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.node.codec.CodecException;
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.TransportServiceCallback;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
@ -57,39 +55,21 @@ import java.util.ArrayList;
import java.util.List;
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
@Component
@TbLwM2mTransportComponent
public class LwM2mTransportContextServer extends TransportContext {
@RequiredArgsConstructor
public class LwM2mTransportServerHelper {
private final LwM2MTransportConfigServer lwM2MTransportConfigServer;
private final LwM2mTransportContext context;
private final TransportService transportService;
private final TransportResourceCache transportResourceCache;
@Getter
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
*
@ -134,7 +114,7 @@ public class LwM2mTransportContextServer extends TransportContext {
*/
public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
return SessionInfoProto.newBuilder()
.setNodeId(this.getNodeId())
.setNodeId(context.getNodeId())
.setSessionIdMSB(mostSignificantBits)
.setSessionIdLSB(leastSignificantBits)
.setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB())
@ -165,8 +145,8 @@ public class LwM2mTransportContextServer extends TransportContext {
* @param logMsg - info about Logs
* @return- KeyValueProto for telemetry (Logs)
*/
public List <TransportProtos.KeyValueProto> getKvLogyToThingsboard(String logMsg) {
List <TransportProtos.KeyValueProto> result = new ArrayList<>();
public List<TransportProtos.KeyValueProto> getKvLogyToThingsboard(String logMsg) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>();
result.add(TransportProtos.KeyValueProto.newBuilder()
.setKey(LOG_LW2M_TELEMETRY)
.setType(TransportProtos.KeyValueType.STRING_V)
@ -179,32 +159,31 @@ public class LwM2mTransportContextServer extends TransportContext {
* @throws CodecException -
*/
public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) {
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName);
if (isMultiInstances) {
kvProto.setType(TransportProtos.KeyValueType.JSON_V)
.setJsonV((String) value);
public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) {
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName);
if (isMultiInstances) {
kvProto.setType(TransportProtos.KeyValueType.JSON_V)
.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);
}
public Object getValueFromKvProto (TransportProtos.KeyValueProto kv) {
public Object getValueFromKvProto(TransportProtos.KeyValueProto kv) {
switch (kv.getType()) {
case BOOLEAN_V:
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
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;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
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;
@Slf4j
@RequiredArgsConstructor
public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
/**
@ -46,12 +48,8 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
* Value = TenantId
*/
private final LwM2mClientContext lwM2mClientContext;
private final LwM2mTransportContextServer lwM2mTransportContextServer;
public LwM2mVersionedModelProvider(LwM2mClientContext lwM2mClientContext, LwM2mTransportContextServer lwM2mTransportContextServer) {
this.lwM2mClientContext = lwM2mClientContext;
this.lwM2mTransportContextServer = lwM2mTransportContextServer;
}
private final LwM2mTransportServerHelper helper;
private final LwM2mTransportContext context;
private String getKeyIdVer(Integer objectId, String version) {
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) {
String key = getKeyIdVer(objectId, version);
Optional<TbResource> tbResource = lwM2mTransportContextServer
.getTransportResourceCache()
.get(this.tenantId, LWM2M_MODEL, key);
Optional<TbResource> tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key);
return tbResource.map(resource -> lwM2mTransportContextServer.parseFromXmlToObjectModel(
return tbResource.map(resource -> helper.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()),
key + ".xml",
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.ValidateDeviceCredentialsResponseMsg;
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 java.util.Collection;
@ -42,9 +42,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
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.LwM2mTransportHandler.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getVerFromPathIdVerOrId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.TRANSPORT_DEFAULT_LWM2M_VERSION;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getVerFromPathIdVerOrId;
@Slf4j
@Data
@ -172,7 +172,7 @@ public class LwM2mClient implements Cloneable {
.collect(Collectors.toSet());
}
public void initReadValue(LwM2mTransportServiceImpl serviceImpl, String path) {
public void initReadValue(DefaultLwM2MTransportMsgHandler serviceImpl, String path) {
if (path != null) {
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.LwM2mCredentialsSecurityInfoValidator;
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.Map;
@ -34,7 +34,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
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
@TbLwM2mTransportComponent
@ -118,7 +118,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
*/
@Override
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) {
UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null;
LwM2mClient client;
@ -165,7 +165,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override
public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandler.getLwM2MClientProfileFromThingsboard(deviceProfile);
LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandlerUtil.getLwM2MClientProfileFromThingsboard(deviceProfile);
if (lwM2MClientProfile != null) {
profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile);
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.thingsboard.server.gen.transport.TransportProtos;
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 static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validPathIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validPathIdVer;
@Data
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
private ExecutorService executor;
@Getter
@Autowired
private FirmwareDataCache firmwareDataCache;
@Autowired
private TransportResourceCache transportResourceCache;
@PostConstruct
public void init() {
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.
-->
<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">
<tb-hotkeys-cheatsheet #cheatSheetComponent></tb-hotkeys-cheatsheet>
<section class="tb-dashboard-toolbar"
@ -25,7 +25,7 @@
[toolbarOpened]="toolbarOpened" (triggerClick)="openToolbar()">
<div class="tb-dashboard-action-panels" fxLayout="column" fxLayout.gt-sm="row"
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">
<tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true">
</tb-user-menu>
@ -49,7 +49,7 @@
<tb-states-component fxFlex.lt-md
[statesControllerId]="isEdit ? 'default' : dashboardConfiguration.settings.stateControllerId"
[dashboardCtrl]="this"
[dashboardId]="(!embedded && dashboard.id) ? dashboard.id.id : ''"
[dashboardId]="setStateDashboardId ? dashboard.id.id : ''"
[isMobile]="isMobile"
[state]="dashboardCtx.state"
[currentState]="currentState"

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

@ -18,6 +18,7 @@
$toolbar-height: 50px !default;
$fullscreen-toolbar-height: 64px !default;
$mobile-toolbar-height: 84px !default;
$mobile-app-toolbar-height: 40px !default;
tb-dashboard-page {
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-layouts {
width: 100%;

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

@ -120,7 +120,8 @@ import {
DisplayWidgetTypesPanelData
} from '@home/components/dashboard-page/widget-types-panel.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
@Component({
@ -159,6 +160,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
singlePageMode: boolean;
forceFullscreen = this.authState.forceFullscreen;
isMobileApp = this.mobileService.isMobileApp();
isFullscreen = false;
isEdit = false;
isEditingWidget = false;
@ -189,6 +191,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
currentCustomerId: string;
currentDashboardScope: DashboardPageScope;
setStateDashboardId = false;
addingLayoutCtx: DashboardPageLayoutContext;
logo = 'assets/logo_title_white.svg';
@ -284,6 +288,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private dashboardService: DashboardService,
private itembuffer: ItemBufferService,
private importExport: ImportExportService,
private mobileService: MobileService,
private fb: FormBuilder,
private dialog: MatDialog,
private translate: TranslateService,
@ -322,6 +327,19 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
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;
if (this.route.snapshot.params.customerId) {
@ -332,7 +350,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.currentCustomerId = this.authUser.customerId;
}
this.dashboard = data.dashboard;
this.dashboardConfiguration = this.dashboard.configuration;
this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow;
this.layouts.main.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboardCtx);
@ -388,6 +405,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.currentCustomerId = null;
this.currentDashboardScope = null;
this.setStateDashboardId = false;
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;
$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 {
mat-fab-toolbar {
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 { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityService } from '@core/http/entity.service';
import { MobileService } from '@core/services/mobile.service';
@Component({
selector: 'tb-default-state-controller',
@ -40,6 +41,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
protected statesControllerService: StatesControllerService,
private utils: UtilsService,
private entityService: EntityService,
private mobileService: MobileService,
private dashboardUtils: DashboardUtilsService) {
super(router, route, ngZone, statesControllerService);
}
@ -229,6 +231,9 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
if (this.dashboardCtrl.dashboardCtx.state !== stateId) {
this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
if (stateId && this.statesValue[stateId]) {
this.mobileService.handleDashboardStateName(this.getStateName(stateId, this.statesValue[stateId]));
}
if (update) {
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 { EntityType } from '@shared/models/entity-type.models';
import { map, tap } from 'rxjs/operators';
import { MobileService } from '@core/services/mobile.service';
@Component({
selector: 'tb-entity-state-controller',
@ -44,6 +45,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
protected statesControllerService: StatesControllerService,
private utils: UtilsService,
private entityService: EntityService,
private mobileService: MobileService,
private dashboardUtils: DashboardUtilsService) {
super(router, route, ngZone, statesControllerService);
}
@ -270,6 +272,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
this.mobileService.handleDashboardStateName(this.getStateName(this.stateObject.length - 1));
if (update) {
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 { 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 { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { CustomDialogContainerComponent } from '@home/components/widget/dialog/custom-dialog-container.component';
import { ImportExportService } from '@home/components/import-export/import-export.service';
@ -185,6 +186,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu
WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent,
MobileActionEditorComponent,
CustomDialogContainerComponent,
ImportDialogComponent,
ImportDialogCsvComponent,
@ -300,6 +302,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu
WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent,
MobileActionEditorComponent,
CustomDialogContainerComponent,
ImportDialogComponent,
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">
</tb-custom-action-pretty-editor>
</ng-template>
<ng-template [ngSwitchCase]="widgetActionType.mobileAction">
<tb-mobile-action-editor #mobileActionEditor
formControlName="mobileAction">
</tb-mobile-action-editor>
</ng-template>
</section>
</fieldset>
</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 { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { isDefinedAndNotNull } from '@core/utils';
import { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component';
export interface WidgetActionDialogData {
isAdd: boolean;
@ -64,6 +65,8 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
@ViewChild('dashboardStateInput') dashboardStateInput: ElementRef;
@ViewChild('mobileActionEditor', {static: false}) mobileActionEditor: MobileActionEditorComponent;
widgetActionFormGroup: FormGroup;
actionTypeFormGroup: FormGroup;
@ -197,6 +200,12 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
this.fb.control(toCustomAction(action), [Validators.required])
);
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 {
this.submitted = true;
const type: WidgetActionType = this.widgetActionFormGroup.get('type').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};
if (this.mobileActionEditor != null) {
this.mobileActionEditor.validateOnSubmit();
}
if (this.widgetActionFormGroup.valid && this.actionTypeFormGroup.valid) {
const type: WidgetActionType = this.widgetActionFormGroup.get('type').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,
LegendConfig,
LegendData,
LegendPosition,
LegendPosition, MobileActionResult,
Widget,
WidgetActionDescriptor,
widgetActionSources,
WidgetActionType,
WidgetComparisonSettings,
WidgetComparisonSettings, WidgetMobileActionDescriptor, WidgetMobileActionType,
WidgetResource,
widgetType,
WidgetTypeParameters
@ -77,7 +77,7 @@ import { EntityId } from '@shared/models/id/entity-id';
import { ActivatedRoute, Router } from '@angular/router';
import cssjs from '@core/css/css';
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 { TimeService } from '@core/services/time.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 { ComponentType } from '@angular/cdk/portal';
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({
selector: 'tb-widget',
@ -177,6 +179,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
private alarmDataService: AlarmDataService,
private translate: TranslateService,
private utils: UtilsService,
private mobileService: MobileService,
private dialogs: DialogService,
private raf: RafService,
private ngZone: NgZone,
private cd: ChangeDetectorRef) {
@ -1094,6 +1098,178 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
);
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 { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { map, mergeMap } from 'rxjs/operators';
export interface IWidgetAction {
name: string;
@ -238,7 +239,9 @@ export class WidgetContext {
rxjs = {
forkJoin,
of
of,
map,
mergeMap
};
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',
openDashboard = 'openDashboard',
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>(
@ -341,10 +353,90 @@ export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>(
[ WidgetActionType.updateDashboardState, 'widget-action.update-dashboard-state' ],
[ WidgetActionType.openDashboard, 'widget-action.open-dashboard' ],
[ 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 {
customFunction?: string;
customResources?: Array<WidgetResource>;
@ -369,6 +461,7 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
dialogHeight?: number;
setEntityId?: boolean;
stateEntityParamName?: string;
mobileAction?: WidgetMobileActionDescriptor;
}
export interface WidgetComparisonSettings {

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

@ -2721,6 +2721,7 @@
"open-dashboard": "Navigate to other dashboard",
"custom": "Custom action",
"custom-pretty": "Custom action (with HTML template)",
"mobile-action": "Mobile action",
"target-dashboard-state": "Target dashboard state",
"target-dashboard-state-required": "Target dashboard state is required",
"set-entity-from-widget": "Set entity from widget",
@ -2732,7 +2733,19 @@
"dialog-width": "Dialog width in percents relative to viewport width",
"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.",
"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": {
"current": "Current bundle",

Loading…
Cancel
Save