Browse Source

Provision init

pull/3518/head
zbeacon 6 years ago
parent
commit
d228208072
  1. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 267
      application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
  3. 24
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java
  4. 29
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java
  5. 27
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java
  6. 24
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java
  7. 9
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  8. 3
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java
  9. 4
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  10. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java
  11. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java
  12. 57
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java
  13. 23
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java
  14. 20
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java
  15. 39
      common/queue/src/main/proto/queue.proto
  16. 79
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  17. 22
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  18. 8
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java
  19. 5
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  20. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  21. 70
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  22. 9
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  23. 8
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  24. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java
  25. 19
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java
  26. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java
  27. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java

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

@ -706,6 +706,12 @@ public abstract class BaseController {
case ASSIGNED_TO_TENANT: case ASSIGNED_TO_TENANT:
msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
break; break;
case PROVISION_SUCCESS:
msgType = DataConstants.PROVISION_SUCCESS;
break;
case PROVISION_FAILURE:
msgType = DataConstants.PROVISION_FAILURE;
break;
} }
if (!StringUtils.isEmpty(msgType)) { if (!StringUtils.isEmpty(msgType)) {
try { try {

267
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java

@ -0,0 +1,267 @@
/**
* Copyright © 2016-2020 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.service.device;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.device.DeviceProvisionService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.state.DeviceStateService;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Service
@Slf4j
public class DeviceProvisionServiceImpl implements DeviceProvisionService {
protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
private static final String DEVICE_PROVISION_STATE = "provisionState";
private static final String PROVISIONED_STATE = "provisioned";
private static final UserId PROVISION_USER_ID = UserId.fromString(NULL_UUID.toString());
private final ReentrantLock deviceCreationLock = new ReentrantLock();
@Autowired
DeviceProfileDao deviceProfileDao;
@Autowired
DeviceService deviceService;
@Autowired
DeviceCredentialsService deviceCredentialsService;
@Autowired
AttributesService attributesService;
@Autowired
DeviceStateService deviceStateService;
@Autowired
AuditLogService auditLogService;
@Autowired
PartitionService partitionService;
@Override
public ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest) {
DeviceProfile targetProfile = deviceProfileDao.findProfileByTenantIdAndProfileDataProvisionConfigurationPair(
TenantId.SYS_TENANT_ID,
provisionRequest.getCredentials().getProvisionDeviceKey(),
provisionRequest.getCredentials().getProvisionDeviceSecret());
if (targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) {
return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
}
ProvisionDeviceProfileConfiguration currentProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration();
if (!new ProvisionDeviceProfileConfiguration(provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()).equals(currentProfileConfiguration)) {
return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
}
Device device = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName());
switch (currentProfileConfiguration.getStrategy()) {
case CHECK_NEW_DEVICE:
if (device == null) {
return createDevice(provisionRequest, targetProfile);
} else {
log.warn("[{}] The device is present and could not be provisioned once more!", device.getName());
notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
}
case CHECK_PRE_PROVISIONED_DEVICE:
if (device == null) {
log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName());
return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
} else {
return processProvision(device, provisionRequest);
}
default:
throw new RuntimeException("Strategy is not supported - " + currentProfileConfiguration.getStrategy().name());
}
}
private ListenableFuture<ProvisionResponse> processProvision(Device device, ProvisionRequest provisionRequest) {
ListenableFuture<Optional<AttributeKvEntry>> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(),
DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE);
ListenableFuture<Boolean> provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> {
if (optionalAtr.isPresent()) {
String state = optionalAtr.get().getValueAsString();
if (state.equals(PROVISIONED_STATE)) {
return Futures.immediateFuture(true);
} else {
log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state);
return Futures.immediateCancelledFuture();
}
}
return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor());
}, MoreExecutors.directExecutor());
if (provisionedFuture.isCancelled()) {
throw new RuntimeException("Unknown provision state!");
}
return Futures.transform(provisionedFuture, provisioned -> {
if (provisioned) {
notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE);
}
notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS);
}, MoreExecutors.directExecutor());
}
private ListenableFuture<ProvisionResponse> createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
deviceCreationLock.lock();
try {
return processCreateDevice(provisionRequest, profile);
} finally {
deviceCreationLock.unlock();
}
}
private ListenableFuture<ProvisionResponse> processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
if (device == null) {
Device savedDevice = saveDevice(provisionRequest, profile);
deviceStateService.onDeviceAdded(savedDevice);
pushDeviceCreatedEventToRuleEngine(savedDevice);
notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
return Futures.transform(saveProvisionStateAttribute(savedDevice), input ->
new ProvisionResponse(
getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()),
ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor());
}
log.warn("[{}] The device is already provisioned!", device.getName());
notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
}
private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) {
return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE),
System.currentTimeMillis())));
}
private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
Device device = new Device();
device.setName(provisionRequest.getDeviceName());
device.setType(provisionRequest.getDeviceType());
device.setTenantId(profile.getTenantId());
return deviceService.saveDevice(device);
}
private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) {
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
if (!StringUtils.isEmpty(x509CertPubKey)) {
credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
credentials.setCredentialsValue(x509CertPubKey);
return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials);
}
return credentials;
}
private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
pushProvisionEventToRuleEngine(provisionRequest, device, type);
logAction(device.getTenantId(), device, success, provisionRequest);
}
private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) {
try {
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request);
TbMsg msg = new TbMsg(Uuids.timeBased(), type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L);
sendToRuleEngine(device.getTenantId(), msg, null);
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e);
}
}
private void pushDeviceCreatedEventToRuleEngine(Device device) {
try {
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device);
TbMsg msg = new TbMsg(Uuids.timeBased(), DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L);
sendToRuleEngine(device.getTenantId(), msg, null);
} catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e);
}
}
protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator());
TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback);
}
private TbMsgMetaData createTbMsgMetaData(Device device) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("tenantId", device.getTenantId().toString());
return metaData;
}
private void logAction(TenantId tenantId, Device device, boolean success, ProvisionRequest provisionRequest) {
ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE;
auditLogService.logEntityAction(tenantId, null, null, device.getName(), device.getId(), device, actionType, null, provisionRequest);
}
}

24
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
public interface DeviceProvisionService {
ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest);
}

29
common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2020 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.dao.device.provision;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration;
@Data
@AllArgsConstructor
public class ProvisionRequest {
private String deviceName;
private String deviceType;
private String x509CertPubKey;
private ProvisionDeviceProfileConfiguration credentials;
}

27
common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2020 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.dao.device.provision;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.security.DeviceCredentials;
@Data
@AllArgsConstructor
public class ProvisionResponse {
private DeviceCredentials deviceCredentials;
private ProvisionResponseStatus responseStatus;
}

24
common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.dao.device.provision;
public enum ProvisionResponseStatus {
SUCCESS,
NOT_FOUND,
FAILURE
}

9
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -63,6 +63,8 @@ public class DataConstants {
public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String ALARM_CLEAR = "ALARM_CLEAR";
public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS";
public static final String PROVISION_FAILURE = "PROVISION_FAILURE";
public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
@ -70,4 +72,11 @@ public class DataConstants {
public static final String SECRET_KEY_FIELD_NAME = "secretKey"; public static final String SECRET_KEY_FIELD_NAME = "secretKey";
public static final String DURATION_MS_FIELD_NAME = "durationMs"; public static final String DURATION_MS_FIELD_NAME = "durationMs";
public static final String DEVICE_NAME = "deviceName";
public static final String DEVICE_TYPE = "deviceType";
public static final String CERT_PUB_KEY = "x509CertPubKey";
public static final String PROVISION_KEY = "provisionDeviceKey";
public static final String PROVISION_SECRET = "provisionDeviceSecret";
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java

@ -16,5 +16,6 @@
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
public enum DeviceProfileType { public enum DeviceProfileType {
DEFAULT DEFAULT,
PROVISION
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java

@ -42,7 +42,9 @@ public enum ActionType {
LOGOUT(false), LOGOUT(false),
LOCKOUT(false), LOCKOUT(false),
ASSIGNED_FROM_TENANT(false), ASSIGNED_FROM_TENANT(false),
ASSIGNED_TO_TENANT(false); ASSIGNED_TO_TENANT(false),
PROVISION_SUCCESS(false),
PROVISION_FAILURE(false);
private final boolean isRead; private final boolean isRead;

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java

@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.DeviceProfileType;
include = JsonTypeInfo.As.PROPERTY, include = JsonTypeInfo.As.PROPERTY,
property = "type") property = "type")
@JsonSubTypes({ @JsonSubTypes({
@JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")}) @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"),
@JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")})
public interface DeviceProfileConfiguration { public interface DeviceProfileConfiguration {
@JsonIgnore @JsonIgnore

4
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java

@ -31,6 +31,8 @@ public class MqttTopics {
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry";
public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim"; public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim";
public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes";
public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + "/provision";
public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/provision/response";
public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway";
public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect"; public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect";
@ -41,6 +43,8 @@ public class MqttTopics {
public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc"; public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc";
public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request"; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request";
public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response"; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response";
public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision";
public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision/response";
private MqttTopics() { private MqttTopics() {

57
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2020 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.data.device.profile;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfileType;
import java.util.Objects;
@Data
public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration {
private String provisionDeviceKey;
private String provisionDeviceSecret;
private ProvisionRequestValidationStrategyType strategy;
@Override
public DeviceProfileType getType() {
return DeviceProfileType.PROVISION;
}
@JsonCreator
public ProvisionDeviceProfileConfiguration(@JsonProperty("provisionDeviceKey") String provisionProfileKey, @JsonProperty("provisionDeviceSecret") String provisionProfileSecret) {
this.provisionDeviceKey = provisionProfileKey;
this.provisionDeviceSecret = provisionProfileSecret;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) o;
return provisionDeviceKey.equals(that.provisionDeviceKey) &&
provisionDeviceSecret.equals(that.provisionDeviceSecret);
}
@Override
public int hashCode() {
return Objects.hash(provisionDeviceKey, provisionDeviceSecret);
}
}

23
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.data.device.profile;
import lombok.Data;
@Data
public class ProvisionRequestValidationStrategy {
private final ProvisionRequestValidationStrategyType validationStrategyType;
}

20
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2020 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.data.device.profile;
public enum ProvisionRequestValidationStrategyType {
CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE
}

39
common/queue/src/main/proto/queue.proto

@ -73,6 +73,11 @@ enum KeyValueType {
JSON_V = 4; JSON_V = 4;
} }
enum CredentialsType {
ACCESS_TOKEN = 0;
X509_CERTIFICATE = 1;
}
message KeyValueProto { message KeyValueProto {
string key = 1; string key = 1;
KeyValueType type = 2; KeyValueType type = 2;
@ -241,6 +246,36 @@ message ClaimDeviceMsg {
int64 durationMs = 4; int64 durationMs = 4;
} }
message DeviceCredentialsProto {
int64 deviceIdMSB = 1;
int64 deviceIdLSB = 2;
CredentialsType credentialsType = 3;
string credentialsId = 4;
string credentialsValue = 5;
}
message ProvisionDeviceRequestMsg {
string deviceName = 1;
string deviceType = 2;
string x509CertPubKey = 3;
ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4;
}
message ProvisionDeviceCredentialsMsg {
string provisionDeviceKey = 1;
string provisionDeviceSecret = 2;
}
message ProvisionDeviceResponseMsg {
DeviceCredentialsProto deviceCredentials = 1;
ProvisionResponseStatus provisionResponseStatus = 2;
}
enum ProvisionResponseStatus {
SUCCESS = 0;
NOT_FOUND = 1;
FAILURE = 2;
}
//Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
message SubscriptionInfoProto { message SubscriptionInfoProto {
int64 lastActivityTime = 1; int64 lastActivityTime = 1;
@ -266,6 +301,7 @@ message TransportToDeviceActorMsg {
ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6;
SubscriptionInfoProto subscriptionInfo = 7; SubscriptionInfoProto subscriptionInfo = 7;
ClaimDeviceMsg claimDevice = 8; ClaimDeviceMsg claimDevice = 8;
ProvisionDeviceRequestMsg provisionDevice = 9;
} }
message TransportToRuleEngineMsg { message TransportToRuleEngineMsg {
@ -441,6 +477,7 @@ message TransportApiRequestMsg {
GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4;
GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5;
ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
// ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
} }
/* Response from ThingsBoard Core Service to Transport Service */ /* Response from ThingsBoard Core Service to Transport Service */
@ -449,6 +486,7 @@ message TransportApiResponseMsg {
GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4;
GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5;
// ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6;
} }
/* Messages that are handled by ThingsBoard Core Service */ /* Messages that are handled by ThingsBoard Core Service */
@ -491,4 +529,5 @@ message ToTransportMsg {
ToServerRpcResponseMsg toServerResponse = 7; ToServerRpcResponseMsg toServerResponse = 7;
DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8;
DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9;
ProvisionDeviceResponseMsg provisionResponse = 10;
} }

79
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -38,7 +38,6 @@ import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
@ -52,9 +51,8 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
@ -66,7 +64,6 @@ import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -74,9 +71,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK;
import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
@ -136,10 +133,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
return; return;
} }
deviceSessionCtx.setChannel(ctx); deviceSessionCtx.setChannel(ctx);
if (CONNECT.equals(msg.fixedHeader().messageType())) {
processConnect(ctx, (MqttConnectMessage) msg);
} else if (deviceSessionCtx.isProvisionOnly()) {
processProvisionSessionMsg(ctx, msg);
} else {
processRegularSessionMsg(ctx, msg);
}
}
private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
switch (msg.fixedHeader().messageType()) { switch (msg.fixedHeader().messageType()) {
case CONNECT: case PUBLISH:
processConnect(ctx, (MqttConnectMessage) msg); MqttPublishMessage mqttMsg = (MqttPublishMessage) msg;
String topicName = mqttMsg.variableHeader().topicName();
int msgId = mqttMsg.variableHeader().packetId();
try {
if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) {
TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), provisionRequestMsg, (TransportServiceCallback) new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
} else {
throw new RuntimeException("Unsupported topic for provisioning requests!");
}
} catch (RuntimeException | AdaptorException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
}
break; break;
case PINGREQ:
ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
break;
case DISCONNECT:
ctx.close();
break;
}
}
private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
switch (msg.fixedHeader().messageType()) {
case PUBLISH: case PUBLISH:
processPublish(ctx, (MqttPublishMessage) msg); processPublish(ctx, (MqttPublishMessage) msg);
break; break;
@ -261,6 +293,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}; };
} }
private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
private final ChannelHandlerContext ctx;
private final int msgId;
private final TransportProtos.ProvisionDeviceRequestMsg msg;
DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) {
this.ctx = ctx;
this.msgId = msgId;
this.msg = msg;
}
@Override
public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
log.trace("[{}] Published msg: {}", sessionId, msg);
if (msgId > 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId));
}
try {
adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
} catch (Exception e) {
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
}
}
@Override
public void onError(Throwable e) {
log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
processDisconnect(ctx);
}
}
private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
if (!checkConnected(ctx, mqttMsg)) { if (!checkConnected(ctx, mqttMsg)) {
return; return;
@ -290,6 +353,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_RPC_TOPIC:
case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC:
case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
registerSubQoS(topic, grantedQoSList, reqQoS); registerSubQoS(topic, grantedQoSList, reqQoS);
break; break;
default: default:

22
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse)));
} }
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
}
@Override
public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
try {
return JsonConverter.convertToProvisionRequestMsg(payload);
} catch (IllegalStateException | JsonSyntaxException ex) {
throw new AdaptorException(ex);
}
}
@Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg responseMsg, int requestId) {
return Optional.of(createMqttPublishMsg(ctx,
MqttTopics.GATEWAY_PROVISION_REQUEST_TOPIC,
JsonConverter.toJson(responseMsg, requestId)));
}
private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);

8
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java

@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
@ -63,4 +65,10 @@ public interface MqttTransportAdaptor {
Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException; Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException;
ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse, int requestId) throws AdaptorException;
} }

5
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
private ChannelHandlerContext channel; private ChannelHandlerContext channel;
private final AtomicInteger msgIdSeq = new AtomicInteger(0); private final AtomicInteger msgIdSeq = new AtomicInteger(0);
@Getter
@Setter
private boolean provisionOnly;
private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();

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

@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo
import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
@ -83,6 +84,8 @@ public interface TransportService {
void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, ProvisionDeviceRequestMsg msg, TransportServiceCallback<Void> deviceProvisionCallback);
void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
@ -90,5 +93,4 @@ public interface TransportService {
void reportActivity(SessionInfoProto sessionInfo); void reportActivity(SessionInfoProto sessionInfo);
void deregisterSession(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo);
} }

70
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
@ -53,6 +54,7 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -397,6 +399,34 @@ public class JsonConverter {
} }
} }
public static JsonObject toJson(ProvisionDeviceResponseMsg payload) {
return toJson(payload, false, 0);
}
public static JsonObject toJson(ProvisionDeviceResponseMsg payload, int requestId) {
return toJson(payload, true, requestId);
}
private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
JsonObject result = new JsonObject();
if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) {
result.addProperty("errorMsg", "Provision data was not found!");
} else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) {
result.addProperty("errorMsg", "Failed to provision device!");
} else {
if (toGateway) {
result.addProperty("id", requestId);
}
result.addProperty("deviceId", new DeviceId(
new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString());
result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name());
result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId());
result.addProperty("credentialsValue",
StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue());
}
return result;
}
public static JsonElement toErrorJson(String errorMsg) { public static JsonElement toErrorJson(String errorMsg) {
JsonObject error = new JsonObject(); JsonObject error = new JsonObject();
error.addProperty("error", errorMsg); error.addProperty("error", errorMsg);
@ -498,4 +528,44 @@ public class JsonConverter {
maxStringValueLength = length; maxStringValueLength = length;
} }
public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
JsonElement jsonElement = new JsonParser().parse(json);
if (jsonElement.isJsonObject()) {
return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
} else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement);
}
}
public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) {
return buildProvisionRequestMsg(jo);
}
private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) {
return TransportProtos.ProvisionDeviceRequestMsg.newBuilder()
.setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true))
.setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true))
.setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false))
.setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg(
getStrValue(jo, DataConstants.PROVISION_KEY, true),
getStrValue(jo, DataConstants.PROVISION_SECRET, true)))
.build();
}
private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) {
return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder()
.setProvisionDeviceKey(provisionKey)
.setProvisionDeviceSecret(provisionSecret)
.build();
}
private static String getStrValue(JsonObject jo, String field, boolean requiredField) {
if (jo.has(field)) {
return jo.get(field).getAsString();
} else {
if (requiredField) {
throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!");
}
return "";
}
}
} }

9
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -484,6 +484,15 @@ public class DefaultTransportService implements TransportService {
} }
} }
@Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ProvisionDeviceRequestMsg msg, TransportServiceCallback<Void> callback) {
if (checkLimits(sessionInfo, msg, callback)) {
reportActivityInternal(sessionInfo);
sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
.setProvisionDevice(msg).build(), callback);
}
}
@Override @Override
public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
reportActivityInternal(sessionInfo); reportActivityInternal(sessionInfo);

8
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.audit.sink.AuditLogSink; import org.thingsboard.server.dao.audit.sink.AuditLogSink;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService {
actionData.put("os", os); actionData.put("os", os);
actionData.put("device", device); actionData.put("device", device);
break; break;
case PROVISION_SUCCESS:
case PROVISION_FAILURE:
ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo);
if (request != null) {
actionData.set("provisionRequest", objectMapper.valueToTree(request));
}
break;
} }
return actionData; return actionData;
} }

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java

@ -38,5 +38,9 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> {
DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId);
DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret);
DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret);
DeviceProfile findByName(TenantId tenantId, String profileName); DeviceProfile findByName(TenantId tenantId, String profileName);
} }

19
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java

@ -0,0 +1,19 @@
/**
* Copyright © 2016-2020 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.dao.device;
public class DeviceProvisionServiceImpl {
}

20
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java

@ -22,6 +22,7 @@ import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import java.util.UUID; import java.util.UUID;
@ -57,4 +58,23 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName); DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName);
@Query(value = "SELECT d FROM DeviceProfileEntity d " +
"WHERE d.tenantId = :tenantId " +
"AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " +
"AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}",
nativeQuery = true)
DeviceProfileEntity findProfileByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId,
@Param("provisionDeviceKey") String provisionDeviceKey,
@Param("provisionDeviceSecret") String provisionDeviceSecret);
@Query(value = "SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " +
" FROM DeviceProfileEntity d " +
"WHERE d.tenantId = :tenantId " +
"AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " +
"AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}",
nativeQuery = true)
DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId,
@Param("provisionDeviceKey") String provisionDeviceKey,
@Param("provisionDeviceSecret") String provisionDeviceSecret);
} }

10
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java

@ -80,6 +80,16 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
return deviceProfileRepository.findDefaultDeviceProfileInfo(tenantId.getId()); return deviceProfileRepository.findDefaultDeviceProfileInfo(tenantId.getId());
} }
@Override
public DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) {
return DaoUtil.getData(deviceProfileRepository.findProfileByTenantIdAndProfileDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret));
}
@Override
public DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) {
return deviceProfileRepository.findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret);
}
@Override @Override
public DeviceProfile findByName(TenantId tenantId, String profileName) { public DeviceProfile findByName(TenantId tenantId, String profileName) {
return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName)); return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));

Loading…
Cancel
Save