Showing
27 changed files
with
786 additions
and
11 deletions
... | ... | @@ -706,6 +706,12 @@ public abstract class BaseController { |
706 | 706 | case ASSIGNED_TO_TENANT: |
707 | 707 | msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; |
708 | 708 | break; |
709 | + case PROVISION_SUCCESS: | |
710 | + msgType = DataConstants.PROVISION_SUCCESS; | |
711 | + break; | |
712 | + case PROVISION_FAILURE: | |
713 | + msgType = DataConstants.PROVISION_FAILURE; | |
714 | + break; | |
709 | 715 | } |
710 | 716 | if (!StringUtils.isEmpty(msgType)) { |
711 | 717 | try { | ... | ... |
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.device; | |
17 | + | |
18 | +import com.datastax.oss.driver.api.core.uuid.Uuids; | |
19 | +import com.fasterxml.jackson.core.JsonProcessingException; | |
20 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
21 | +import com.google.common.util.concurrent.Futures; | |
22 | +import com.google.common.util.concurrent.ListenableFuture; | |
23 | +import com.google.common.util.concurrent.MoreExecutors; | |
24 | +import lombok.extern.slf4j.Slf4j; | |
25 | +import org.springframework.beans.factory.annotation.Autowired; | |
26 | +import org.springframework.stereotype.Service; | |
27 | +import org.springframework.util.StringUtils; | |
28 | +import org.thingsboard.server.common.data.DataConstants; | |
29 | +import org.thingsboard.server.common.data.Device; | |
30 | +import org.thingsboard.server.common.data.DeviceProfile; | |
31 | +import org.thingsboard.server.common.data.DeviceProfileType; | |
32 | +import org.thingsboard.server.common.data.audit.ActionType; | |
33 | +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; | |
34 | +import org.thingsboard.server.common.data.id.TenantId; | |
35 | +import org.thingsboard.server.common.data.id.UserId; | |
36 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
37 | +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; | |
38 | +import org.thingsboard.server.common.data.kv.StringDataEntry; | |
39 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | |
40 | +import org.thingsboard.server.common.data.security.DeviceCredentialsType; | |
41 | +import org.thingsboard.server.common.msg.TbMsg; | |
42 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | |
43 | +import org.thingsboard.server.common.msg.queue.ServiceType; | |
44 | +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; | |
45 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
46 | +import org.thingsboard.server.dao.audit.AuditLogService; | |
47 | +import org.thingsboard.server.dao.device.DeviceCredentialsService; | |
48 | +import org.thingsboard.server.dao.device.DeviceProfileDao; | |
49 | +import org.thingsboard.server.dao.device.DeviceProvisionService; | |
50 | +import org.thingsboard.server.dao.device.DeviceService; | |
51 | +import org.thingsboard.server.dao.device.provision.ProvisionRequest; | |
52 | +import org.thingsboard.server.dao.device.provision.ProvisionResponse; | |
53 | +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; | |
54 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; | |
55 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
56 | +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; | |
57 | +import org.thingsboard.server.queue.TbQueueCallback; | |
58 | +import org.thingsboard.server.queue.TbQueueProducer; | |
59 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
60 | +import org.thingsboard.server.queue.discovery.PartitionService; | |
61 | +import org.thingsboard.server.service.state.DeviceStateService; | |
62 | + | |
63 | +import java.util.Collections; | |
64 | +import java.util.List; | |
65 | +import java.util.Optional; | |
66 | +import java.util.concurrent.locks.ReentrantLock; | |
67 | + | |
68 | +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; | |
69 | + | |
70 | + | |
71 | +@Service | |
72 | +@Slf4j | |
73 | +public class DeviceProvisionServiceImpl implements DeviceProvisionService { | |
74 | + | |
75 | + protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer; | |
76 | + | |
77 | + private static final String DEVICE_PROVISION_STATE = "provisionState"; | |
78 | + private static final String PROVISIONED_STATE = "provisioned"; | |
79 | + | |
80 | + private static final UserId PROVISION_USER_ID = UserId.fromString(NULL_UUID.toString()); | |
81 | + | |
82 | + private final ReentrantLock deviceCreationLock = new ReentrantLock(); | |
83 | + | |
84 | + @Autowired | |
85 | + DeviceProfileDao deviceProfileDao; | |
86 | + | |
87 | + @Autowired | |
88 | + DeviceService deviceService; | |
89 | + | |
90 | + @Autowired | |
91 | + DeviceCredentialsService deviceCredentialsService; | |
92 | + | |
93 | + @Autowired | |
94 | + AttributesService attributesService; | |
95 | + | |
96 | + @Autowired | |
97 | + DeviceStateService deviceStateService; | |
98 | + | |
99 | + @Autowired | |
100 | + AuditLogService auditLogService; | |
101 | + | |
102 | + @Autowired | |
103 | + PartitionService partitionService; | |
104 | + | |
105 | + | |
106 | + @Override | |
107 | + public ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest) { | |
108 | + DeviceProfile targetProfile = deviceProfileDao.findProfileByTenantIdAndProfileDataProvisionConfigurationPair( | |
109 | + TenantId.SYS_TENANT_ID, | |
110 | + provisionRequest.getCredentials().getProvisionDeviceKey(), | |
111 | + provisionRequest.getCredentials().getProvisionDeviceSecret()); | |
112 | + | |
113 | + if (targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { | |
114 | + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); | |
115 | + } | |
116 | + | |
117 | + ProvisionDeviceProfileConfiguration currentProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration(); | |
118 | + if (!new ProvisionDeviceProfileConfiguration(provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()).equals(currentProfileConfiguration)) { | |
119 | + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); | |
120 | + } | |
121 | + | |
122 | + Device device = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); | |
123 | + switch (currentProfileConfiguration.getStrategy()) { | |
124 | + case CHECK_NEW_DEVICE: | |
125 | + if (device == null) { | |
126 | + return createDevice(provisionRequest, targetProfile); | |
127 | + } else { | |
128 | + log.warn("[{}] The device is present and could not be provisioned once more!", device.getName()); | |
129 | + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); | |
130 | + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); | |
131 | + } | |
132 | + case CHECK_PRE_PROVISIONED_DEVICE: | |
133 | + if (device == null) { | |
134 | + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); | |
135 | + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); | |
136 | + } else { | |
137 | + return processProvision(device, provisionRequest); | |
138 | + } | |
139 | + default: | |
140 | + throw new RuntimeException("Strategy is not supported - " + currentProfileConfiguration.getStrategy().name()); | |
141 | + } | |
142 | + } | |
143 | + | |
144 | + private ListenableFuture<ProvisionResponse> processProvision(Device device, ProvisionRequest provisionRequest) { | |
145 | + ListenableFuture<Optional<AttributeKvEntry>> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), | |
146 | + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); | |
147 | + ListenableFuture<Boolean> provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { | |
148 | + if (optionalAtr.isPresent()) { | |
149 | + String state = optionalAtr.get().getValueAsString(); | |
150 | + if (state.equals(PROVISIONED_STATE)) { | |
151 | + return Futures.immediateFuture(true); | |
152 | + } else { | |
153 | + log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); | |
154 | + return Futures.immediateCancelledFuture(); | |
155 | + } | |
156 | + } | |
157 | + return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); | |
158 | + }, MoreExecutors.directExecutor()); | |
159 | + if (provisionedFuture.isCancelled()) { | |
160 | + throw new RuntimeException("Unknown provision state!"); | |
161 | + } | |
162 | + return Futures.transform(provisionedFuture, provisioned -> { | |
163 | + if (provisioned) { | |
164 | + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); | |
165 | + return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE); | |
166 | + } | |
167 | + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); | |
168 | + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); | |
169 | + }, MoreExecutors.directExecutor()); | |
170 | + } | |
171 | + | |
172 | + private ListenableFuture<ProvisionResponse> createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { | |
173 | + deviceCreationLock.lock(); | |
174 | + try { | |
175 | + return processCreateDevice(provisionRequest, profile); | |
176 | + } finally { | |
177 | + deviceCreationLock.unlock(); | |
178 | + } | |
179 | + } | |
180 | + | |
181 | + private ListenableFuture<ProvisionResponse> processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { | |
182 | + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); | |
183 | + if (device == null) { | |
184 | + Device savedDevice = saveDevice(provisionRequest, profile); | |
185 | + | |
186 | + deviceStateService.onDeviceAdded(savedDevice); | |
187 | + pushDeviceCreatedEventToRuleEngine(savedDevice); | |
188 | + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); | |
189 | + | |
190 | + return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> | |
191 | + new ProvisionResponse( | |
192 | + getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()), | |
193 | + ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); | |
194 | + } | |
195 | + log.warn("[{}] The device is already provisioned!", device.getName()); | |
196 | + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); | |
197 | + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); | |
198 | + } | |
199 | + | |
200 | + private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) { | |
201 | + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, | |
202 | + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), | |
203 | + System.currentTimeMillis()))); | |
204 | + } | |
205 | + | |
206 | + private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { | |
207 | + Device device = new Device(); | |
208 | + device.setName(provisionRequest.getDeviceName()); | |
209 | + device.setType(provisionRequest.getDeviceType()); | |
210 | + device.setTenantId(profile.getTenantId()); | |
211 | + return deviceService.saveDevice(device); | |
212 | + } | |
213 | + | |
214 | + private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) { | |
215 | + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); | |
216 | + if (!StringUtils.isEmpty(x509CertPubKey)) { | |
217 | + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); | |
218 | + credentials.setCredentialsValue(x509CertPubKey); | |
219 | + return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials); | |
220 | + } | |
221 | + return credentials; | |
222 | + } | |
223 | + | |
224 | + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { | |
225 | + pushProvisionEventToRuleEngine(provisionRequest, device, type); | |
226 | + logAction(device.getTenantId(), device, success, provisionRequest); | |
227 | + } | |
228 | + | |
229 | + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { | |
230 | + try { | |
231 | + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request); | |
232 | + TbMsg msg = new TbMsg(Uuids.timeBased(), type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L); | |
233 | + sendToRuleEngine(device.getTenantId(), msg, null); | |
234 | + } catch (JsonProcessingException | IllegalArgumentException e) { | |
235 | + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); | |
236 | + } | |
237 | + } | |
238 | + | |
239 | + private void pushDeviceCreatedEventToRuleEngine(Device device) { | |
240 | + try { | |
241 | + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); | |
242 | + TbMsg msg = new TbMsg(Uuids.timeBased(), DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L); | |
243 | + sendToRuleEngine(device.getTenantId(), msg, null); | |
244 | + } catch (JsonProcessingException | IllegalArgumentException e) { | |
245 | + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); | |
246 | + } | |
247 | + } | |
248 | + | |
249 | + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { | |
250 | + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); | |
251 | + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg)) | |
252 | + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) | |
253 | + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build(); | |
254 | + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); | |
255 | + } | |
256 | + | |
257 | + private TbMsgMetaData createTbMsgMetaData(Device device) { | |
258 | + TbMsgMetaData metaData = new TbMsgMetaData(); | |
259 | + metaData.putValue("tenantId", device.getTenantId().toString()); | |
260 | + return metaData; | |
261 | + } | |
262 | + | |
263 | + private void logAction(TenantId tenantId, Device device, boolean success, ProvisionRequest provisionRequest) { | |
264 | + ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; | |
265 | + auditLogService.logEntityAction(tenantId, null, null, device.getName(), device.getId(), device, actionType, null, provisionRequest); | |
266 | + } | |
267 | +} | ... | ... |
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.device; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import org.thingsboard.server.dao.device.provision.ProvisionRequest; | |
20 | +import org.thingsboard.server.dao.device.provision.ProvisionResponse; | |
21 | + | |
22 | +public interface DeviceProvisionService { | |
23 | + ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest); | |
24 | +} | ... | ... |
common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.device.provision; | |
17 | + | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
20 | +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; | |
21 | + | |
22 | +@Data | |
23 | +@AllArgsConstructor | |
24 | +public class ProvisionRequest { | |
25 | + private String deviceName; | |
26 | + private String deviceType; | |
27 | + private String x509CertPubKey; | |
28 | + private ProvisionDeviceProfileConfiguration credentials; | |
29 | +} | ... | ... |
common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.device.provision; | |
17 | + | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
20 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | |
21 | + | |
22 | +@Data | |
23 | +@AllArgsConstructor | |
24 | +public class ProvisionResponse { | |
25 | + private DeviceCredentials deviceCredentials; | |
26 | + private ProvisionResponseStatus responseStatus; | |
27 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.device.provision; | |
17 | + | |
18 | +public enum ProvisionResponseStatus { | |
19 | + | |
20 | + SUCCESS, | |
21 | + NOT_FOUND, | |
22 | + FAILURE | |
23 | + | |
24 | +} | ... | ... |
... | ... | @@ -63,6 +63,8 @@ public class DataConstants { |
63 | 63 | public static final String ALARM_CLEAR = "ALARM_CLEAR"; |
64 | 64 | public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; |
65 | 65 | public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; |
66 | + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; | |
67 | + public static final String PROVISION_FAILURE = "PROVISION_FAILURE"; | |
66 | 68 | |
67 | 69 | public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; |
68 | 70 | |
... | ... | @@ -70,4 +72,11 @@ public class DataConstants { |
70 | 72 | public static final String SECRET_KEY_FIELD_NAME = "secretKey"; |
71 | 73 | public static final String DURATION_MS_FIELD_NAME = "durationMs"; |
72 | 74 | |
75 | + public static final String DEVICE_NAME = "deviceName"; | |
76 | + public static final String DEVICE_TYPE = "deviceType"; | |
77 | + public static final String CERT_PUB_KEY = "x509CertPubKey"; | |
78 | + | |
79 | + public static final String PROVISION_KEY = "provisionDeviceKey"; | |
80 | + public static final String PROVISION_SECRET = "provisionDeviceSecret"; | |
81 | + | |
73 | 82 | } | ... | ... |
... | ... | @@ -42,7 +42,9 @@ public enum ActionType { |
42 | 42 | LOGOUT(false), |
43 | 43 | LOCKOUT(false), |
44 | 44 | ASSIGNED_FROM_TENANT(false), |
45 | - ASSIGNED_TO_TENANT(false); | |
45 | + ASSIGNED_TO_TENANT(false), | |
46 | + PROVISION_SUCCESS(false), | |
47 | + PROVISION_FAILURE(false); | |
46 | 48 | |
47 | 49 | private final boolean isRead; |
48 | 50 | ... | ... |
... | ... | @@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.DeviceProfileType; |
27 | 27 | include = JsonTypeInfo.As.PROPERTY, |
28 | 28 | property = "type") |
29 | 29 | @JsonSubTypes({ |
30 | - @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")}) | |
30 | + @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"), | |
31 | + @JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")}) | |
31 | 32 | public interface DeviceProfileConfiguration { |
32 | 33 | |
33 | 34 | @JsonIgnore | ... | ... |
... | ... | @@ -31,6 +31,8 @@ public class MqttTopics { |
31 | 31 | public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; |
32 | 32 | public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim"; |
33 | 33 | public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; |
34 | + public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + "/provision"; | |
35 | + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/provision/response"; | |
34 | 36 | |
35 | 37 | public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; |
36 | 38 | public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect"; |
... | ... | @@ -41,6 +43,8 @@ public class MqttTopics { |
41 | 43 | public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc"; |
42 | 44 | public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request"; |
43 | 45 | public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response"; |
46 | + public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision"; | |
47 | + public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision/response"; | |
44 | 48 | |
45 | 49 | |
46 | 50 | private MqttTopics() { | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.profile; | |
17 | + | |
18 | +import com.fasterxml.jackson.annotation.JsonCreator; | |
19 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
20 | +import lombok.Data; | |
21 | +import org.thingsboard.server.common.data.DeviceProfileType; | |
22 | + | |
23 | +import java.util.Objects; | |
24 | + | |
25 | +@Data | |
26 | +public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration { | |
27 | + | |
28 | + private String provisionDeviceKey; | |
29 | + private String provisionDeviceSecret; | |
30 | + | |
31 | + private ProvisionRequestValidationStrategyType strategy; | |
32 | + | |
33 | + @Override | |
34 | + public DeviceProfileType getType() { | |
35 | + return DeviceProfileType.PROVISION; | |
36 | + } | |
37 | + | |
38 | + @JsonCreator | |
39 | + public ProvisionDeviceProfileConfiguration(@JsonProperty("provisionDeviceKey") String provisionProfileKey, @JsonProperty("provisionDeviceSecret") String provisionProfileSecret) { | |
40 | + this.provisionDeviceKey = provisionProfileKey; | |
41 | + this.provisionDeviceSecret = provisionProfileSecret; | |
42 | + } | |
43 | + | |
44 | + @Override | |
45 | + public boolean equals(Object o) { | |
46 | + if (this == o) return true; | |
47 | + if (o == null || getClass() != o.getClass()) return false; | |
48 | + ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) o; | |
49 | + return provisionDeviceKey.equals(that.provisionDeviceKey) && | |
50 | + provisionDeviceSecret.equals(that.provisionDeviceSecret); | |
51 | + } | |
52 | + | |
53 | + @Override | |
54 | + public int hashCode() { | |
55 | + return Objects.hash(provisionDeviceKey, provisionDeviceSecret); | |
56 | + } | |
57 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.profile; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class ProvisionRequestValidationStrategy { | |
22 | + private final ProvisionRequestValidationStrategyType validationStrategyType; | |
23 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.profile; | |
17 | + | |
18 | +public enum ProvisionRequestValidationStrategyType { | |
19 | + CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE | |
20 | +} | ... | ... |
... | ... | @@ -73,6 +73,11 @@ enum KeyValueType { |
73 | 73 | JSON_V = 4; |
74 | 74 | } |
75 | 75 | |
76 | +enum CredentialsType { | |
77 | + ACCESS_TOKEN = 0; | |
78 | + X509_CERTIFICATE = 1; | |
79 | +} | |
80 | + | |
76 | 81 | message KeyValueProto { |
77 | 82 | string key = 1; |
78 | 83 | KeyValueType type = 2; |
... | ... | @@ -241,6 +246,36 @@ message ClaimDeviceMsg { |
241 | 246 | int64 durationMs = 4; |
242 | 247 | } |
243 | 248 | |
249 | +message DeviceCredentialsProto { | |
250 | + int64 deviceIdMSB = 1; | |
251 | + int64 deviceIdLSB = 2; | |
252 | + CredentialsType credentialsType = 3; | |
253 | + string credentialsId = 4; | |
254 | + string credentialsValue = 5; | |
255 | +} | |
256 | + | |
257 | +message ProvisionDeviceRequestMsg { | |
258 | + string deviceName = 1; | |
259 | + string deviceType = 2; | |
260 | + string x509CertPubKey = 3; | |
261 | + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4; | |
262 | +} | |
263 | + | |
264 | +message ProvisionDeviceCredentialsMsg { | |
265 | + string provisionDeviceKey = 1; | |
266 | + string provisionDeviceSecret = 2; | |
267 | +} | |
268 | + | |
269 | +message ProvisionDeviceResponseMsg { | |
270 | + DeviceCredentialsProto deviceCredentials = 1; | |
271 | + ProvisionResponseStatus provisionResponseStatus = 2; | |
272 | +} | |
273 | + | |
274 | +enum ProvisionResponseStatus { | |
275 | + SUCCESS = 0; | |
276 | + NOT_FOUND = 1; | |
277 | + FAILURE = 2; | |
278 | +} | |
244 | 279 | //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. |
245 | 280 | message SubscriptionInfoProto { |
246 | 281 | int64 lastActivityTime = 1; |
... | ... | @@ -266,6 +301,7 @@ message TransportToDeviceActorMsg { |
266 | 301 | ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; |
267 | 302 | SubscriptionInfoProto subscriptionInfo = 7; |
268 | 303 | ClaimDeviceMsg claimDevice = 8; |
304 | + ProvisionDeviceRequestMsg provisionDevice = 9; | |
269 | 305 | } |
270 | 306 | |
271 | 307 | message TransportToRuleEngineMsg { |
... | ... | @@ -441,6 +477,7 @@ message TransportApiRequestMsg { |
441 | 477 | GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; |
442 | 478 | GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; |
443 | 479 | ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; |
480 | +// ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; | |
444 | 481 | } |
445 | 482 | |
446 | 483 | /* Response from ThingsBoard Core Service to Transport Service */ |
... | ... | @@ -449,6 +486,7 @@ message TransportApiResponseMsg { |
449 | 486 | GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; |
450 | 487 | GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; |
451 | 488 | GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; |
489 | +// ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; | |
452 | 490 | } |
453 | 491 | |
454 | 492 | /* Messages that are handled by ThingsBoard Core Service */ |
... | ... | @@ -491,4 +529,5 @@ message ToTransportMsg { |
491 | 529 | ToServerRpcResponseMsg toServerResponse = 7; |
492 | 530 | DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; |
493 | 531 | DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; |
532 | + ProvisionDeviceResponseMsg provisionResponse = 10; | |
494 | 533 | } | ... | ... |
... | ... | @@ -38,7 +38,6 @@ import io.netty.util.ReferenceCountUtil; |
38 | 38 | import io.netty.util.concurrent.Future; |
39 | 39 | import io.netty.util.concurrent.GenericFutureListener; |
40 | 40 | import lombok.extern.slf4j.Slf4j; |
41 | -import org.springframework.util.StringUtils; | |
42 | 41 | import org.thingsboard.server.common.data.DeviceProfile; |
43 | 42 | import org.thingsboard.server.common.data.DeviceTransportType; |
44 | 43 | import org.thingsboard.server.common.data.device.profile.MqttTopics; |
... | ... | @@ -52,9 +51,8 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; |
52 | 51 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
53 | 52 | import org.thingsboard.server.common.transport.service.DefaultTransportService; |
54 | 53 | import org.thingsboard.server.gen.transport.TransportProtos; |
54 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; | |
55 | 55 | import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; |
56 | -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; | |
57 | -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; | |
58 | 56 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; |
59 | 57 | import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; |
60 | 58 | import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; |
... | ... | @@ -66,7 +64,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; |
66 | 64 | import javax.security.cert.X509Certificate; |
67 | 65 | import java.io.IOException; |
68 | 66 | import java.net.InetSocketAddress; |
69 | -import java.nio.charset.StandardCharsets; | |
70 | 67 | import java.util.ArrayList; |
71 | 68 | import java.util.List; |
72 | 69 | import java.util.UUID; |
... | ... | @@ -74,9 +71,9 @@ import java.util.concurrent.ConcurrentHashMap; |
74 | 71 | import java.util.concurrent.ConcurrentMap; |
75 | 72 | |
76 | 73 | import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; |
77 | -import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD; | |
78 | 74 | import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; |
79 | 75 | import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; |
76 | +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT; | |
80 | 77 | import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; |
81 | 78 | import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; |
82 | 79 | import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; |
... | ... | @@ -136,10 +133,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
136 | 133 | return; |
137 | 134 | } |
138 | 135 | deviceSessionCtx.setChannel(ctx); |
136 | + if (CONNECT.equals(msg.fixedHeader().messageType())) { | |
137 | + processConnect(ctx, (MqttConnectMessage) msg); | |
138 | + } else if (deviceSessionCtx.isProvisionOnly()) { | |
139 | + processProvisionSessionMsg(ctx, msg); | |
140 | + } else { | |
141 | + processRegularSessionMsg(ctx, msg); | |
142 | + } | |
143 | + } | |
144 | + | |
145 | + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { | |
139 | 146 | switch (msg.fixedHeader().messageType()) { |
140 | - case CONNECT: | |
141 | - processConnect(ctx, (MqttConnectMessage) msg); | |
147 | + case PUBLISH: | |
148 | + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg; | |
149 | + String topicName = mqttMsg.variableHeader().topicName(); | |
150 | + int msgId = mqttMsg.variableHeader().packetId(); | |
151 | + try { | |
152 | + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { | |
153 | + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); | |
154 | + transportService.process(deviceSessionCtx.getSessionInfo(), provisionRequestMsg, (TransportServiceCallback) new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); | |
155 | + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); | |
156 | + } else { | |
157 | + throw new RuntimeException("Unsupported topic for provisioning requests!"); | |
158 | + } | |
159 | + } catch (RuntimeException | AdaptorException e) { | |
160 | + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); | |
161 | + ctx.close(); | |
162 | + } | |
142 | 163 | break; |
164 | + case PINGREQ: | |
165 | + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); | |
166 | + break; | |
167 | + case DISCONNECT: | |
168 | + ctx.close(); | |
169 | + break; | |
170 | + } | |
171 | + } | |
172 | + | |
173 | + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { | |
174 | + switch (msg.fixedHeader().messageType()) { | |
143 | 175 | case PUBLISH: |
144 | 176 | processPublish(ctx, (MqttPublishMessage) msg); |
145 | 177 | break; |
... | ... | @@ -261,6 +293,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
261 | 293 | }; |
262 | 294 | } |
263 | 295 | |
296 | + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> { | |
297 | + private final ChannelHandlerContext ctx; | |
298 | + private final int msgId; | |
299 | + private final TransportProtos.ProvisionDeviceRequestMsg msg; | |
300 | + | |
301 | + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) { | |
302 | + this.ctx = ctx; | |
303 | + this.msgId = msgId; | |
304 | + this.msg = msg; | |
305 | + } | |
306 | + | |
307 | + @Override | |
308 | + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { | |
309 | + log.trace("[{}] Published msg: {}", sessionId, msg); | |
310 | + if (msgId > 0) { | |
311 | + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); | |
312 | + } | |
313 | + try { | |
314 | + adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
315 | + } catch (Exception e) { | |
316 | + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); | |
317 | + } | |
318 | + } | |
319 | + | |
320 | + @Override | |
321 | + public void onError(Throwable e) { | |
322 | + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); | |
323 | + processDisconnect(ctx); | |
324 | + } | |
325 | + } | |
326 | + | |
264 | 327 | private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { |
265 | 328 | if (!checkConnected(ctx, mqttMsg)) { |
266 | 329 | return; |
... | ... | @@ -290,6 +353,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
290 | 353 | case MqttTopics.GATEWAY_RPC_TOPIC: |
291 | 354 | case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: |
292 | 355 | case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: |
356 | + case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC: | |
357 | + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: | |
293 | 358 | registerSubQoS(topic, grantedQoSList, reqQoS); |
294 | 359 | break; |
295 | 360 | default: | ... | ... |
... | ... | @@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { |
184 | 184 | return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); |
185 | 185 | } |
186 | 186 | |
187 | + @Override | |
188 | + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) { | |
189 | + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); | |
190 | + } | |
191 | + | |
192 | + @Override | |
193 | + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { | |
194 | + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); | |
195 | + try { | |
196 | + return JsonConverter.convertToProvisionRequestMsg(payload); | |
197 | + } catch (IllegalStateException | JsonSyntaxException ex) { | |
198 | + throw new AdaptorException(ex); | |
199 | + } | |
200 | + } | |
201 | + | |
202 | + @Override | |
203 | + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg responseMsg, int requestId) { | |
204 | + return Optional.of(createMqttPublishMsg(ctx, | |
205 | + MqttTopics.GATEWAY_PROVISION_REQUEST_TOPIC, | |
206 | + JsonConverter.toJson(responseMsg, requestId))); | |
207 | + } | |
208 | + | |
187 | 209 | private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { |
188 | 210 | MqttFixedHeader mqttFixedHeader = |
189 | 211 | new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); | ... | ... |
... | ... | @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM |
24 | 24 | import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; |
25 | 25 | import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; |
26 | 26 | import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; |
27 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; | |
28 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; | |
27 | 29 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; |
28 | 30 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; |
29 | 31 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; |
... | ... | @@ -63,4 +65,10 @@ public interface MqttTransportAdaptor { |
63 | 65 | |
64 | 66 | Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException; |
65 | 67 | |
68 | + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; | |
69 | + | |
70 | + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; | |
71 | + | |
72 | + Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse, int requestId) throws AdaptorException; | |
73 | + | |
66 | 74 | } | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session; |
17 | 17 | |
18 | 18 | import io.netty.channel.ChannelHandlerContext; |
19 | 19 | import lombok.Getter; |
20 | +import lombok.Setter; | |
20 | 21 | import lombok.extern.slf4j.Slf4j; |
21 | 22 | import org.thingsboard.server.common.data.DeviceProfile; |
22 | 23 | import org.thingsboard.server.common.data.DeviceTransportType; |
... | ... | @@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { |
40 | 41 | private ChannelHandlerContext channel; |
41 | 42 | private final AtomicInteger msgIdSeq = new AtomicInteger(0); |
42 | 43 | |
44 | + @Getter | |
45 | + @Setter | |
46 | + private boolean provisionOnly; | |
47 | + | |
43 | 48 | private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); |
44 | 49 | private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); |
45 | 50 | ... | ... |
... | ... | @@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo |
27 | 27 | import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; |
28 | 28 | import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; |
29 | 29 | import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; |
30 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; | |
30 | 31 | import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; |
31 | 32 | import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; |
32 | 33 | import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; |
... | ... | @@ -83,6 +84,8 @@ public interface TransportService { |
83 | 84 | |
84 | 85 | void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback); |
85 | 86 | |
87 | + void process(SessionInfoProto sessionInfo, ProvisionDeviceRequestMsg msg, TransportServiceCallback<Void> deviceProvisionCallback); | |
88 | + | |
86 | 89 | void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); |
87 | 90 | |
88 | 91 | void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); |
... | ... | @@ -90,5 +93,4 @@ public interface TransportService { |
90 | 93 | void reportActivity(SessionInfoProto sessionInfo); |
91 | 94 | |
92 | 95 | void deregisterSession(SessionInfoProto sessionInfo); |
93 | - | |
94 | 96 | } | ... | ... |
... | ... | @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; |
42 | 42 | import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; |
43 | 43 | import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; |
44 | 44 | import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; |
45 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; | |
45 | 46 | import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; |
46 | 47 | import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; |
47 | 48 | |
... | ... | @@ -53,6 +54,7 @@ import java.util.Map; |
53 | 54 | import java.util.Map.Entry; |
54 | 55 | import java.util.Set; |
55 | 56 | import java.util.TreeMap; |
57 | +import java.util.UUID; | |
56 | 58 | import java.util.function.Consumer; |
57 | 59 | import java.util.stream.Collectors; |
58 | 60 | |
... | ... | @@ -397,6 +399,34 @@ public class JsonConverter { |
397 | 399 | } |
398 | 400 | } |
399 | 401 | |
402 | + public static JsonObject toJson(ProvisionDeviceResponseMsg payload) { | |
403 | + return toJson(payload, false, 0); | |
404 | + } | |
405 | + | |
406 | + public static JsonObject toJson(ProvisionDeviceResponseMsg payload, int requestId) { | |
407 | + return toJson(payload, true, requestId); | |
408 | + } | |
409 | + | |
410 | + private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { | |
411 | + JsonObject result = new JsonObject(); | |
412 | + if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { | |
413 | + result.addProperty("errorMsg", "Provision data was not found!"); | |
414 | + } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { | |
415 | + result.addProperty("errorMsg", "Failed to provision device!"); | |
416 | + } else { | |
417 | + if (toGateway) { | |
418 | + result.addProperty("id", requestId); | |
419 | + } | |
420 | + result.addProperty("deviceId", new DeviceId( | |
421 | + new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString()); | |
422 | + result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); | |
423 | + result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); | |
424 | + result.addProperty("credentialsValue", | |
425 | + StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); | |
426 | + } | |
427 | + return result; | |
428 | + } | |
429 | + | |
400 | 430 | public static JsonElement toErrorJson(String errorMsg) { |
401 | 431 | JsonObject error = new JsonObject(); |
402 | 432 | error.addProperty("error", errorMsg); |
... | ... | @@ -498,4 +528,44 @@ public class JsonConverter { |
498 | 528 | maxStringValueLength = length; |
499 | 529 | } |
500 | 530 | |
531 | + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { | |
532 | + JsonElement jsonElement = new JsonParser().parse(json); | |
533 | + if (jsonElement.isJsonObject()) { | |
534 | + return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); | |
535 | + } else { | |
536 | + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement); | |
537 | + } | |
538 | + } | |
539 | + | |
540 | + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) { | |
541 | + return buildProvisionRequestMsg(jo); | |
542 | + } | |
543 | + | |
544 | + private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { | |
545 | + return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() | |
546 | + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) | |
547 | + .setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true)) | |
548 | + .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) | |
549 | + .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( | |
550 | + getStrValue(jo, DataConstants.PROVISION_KEY, true), | |
551 | + getStrValue(jo, DataConstants.PROVISION_SECRET, true))) | |
552 | + .build(); | |
553 | + } | |
554 | + | |
555 | + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) { | |
556 | + return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder() | |
557 | + .setProvisionDeviceKey(provisionKey) | |
558 | + .setProvisionDeviceSecret(provisionSecret) | |
559 | + .build(); | |
560 | + } | |
561 | + private static String getStrValue(JsonObject jo, String field, boolean requiredField) { | |
562 | + if (jo.has(field)) { | |
563 | + return jo.get(field).getAsString(); | |
564 | + } else { | |
565 | + if (requiredField) { | |
566 | + throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!"); | |
567 | + } | |
568 | + return ""; | |
569 | + } | |
570 | + } | |
501 | 571 | } | ... | ... |
... | ... | @@ -485,6 +485,15 @@ public class DefaultTransportService implements TransportService { |
485 | 485 | } |
486 | 486 | |
487 | 487 | @Override |
488 | + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ProvisionDeviceRequestMsg msg, TransportServiceCallback<Void> callback) { | |
489 | + if (checkLimits(sessionInfo, msg, callback)) { | |
490 | + reportActivityInternal(sessionInfo); | |
491 | + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) | |
492 | + .setProvisionDevice(msg).build(), callback); | |
493 | + } | |
494 | + } | |
495 | + | |
496 | + @Override | |
488 | 497 | public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { |
489 | 498 | reportActivityInternal(sessionInfo); |
490 | 499 | } | ... | ... |
... | ... | @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; |
47 | 47 | import org.thingsboard.server.dao.audit.sink.AuditLogSink; |
48 | 48 | import org.thingsboard.server.dao.entity.EntityService; |
49 | 49 | import org.thingsboard.server.dao.exception.DataValidationException; |
50 | +import org.thingsboard.server.dao.device.provision.ProvisionRequest; | |
50 | 51 | import org.thingsboard.server.dao.service.DataValidator; |
51 | 52 | |
52 | 53 | import java.io.PrintWriter; |
... | ... | @@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService { |
257 | 258 | actionData.put("os", os); |
258 | 259 | actionData.put("device", device); |
259 | 260 | break; |
261 | + case PROVISION_SUCCESS: | |
262 | + case PROVISION_FAILURE: | |
263 | + ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo); | |
264 | + if (request != null) { | |
265 | + actionData.set("provisionRequest", objectMapper.valueToTree(request)); | |
266 | + } | |
267 | + break; | |
260 | 268 | } |
261 | 269 | return actionData; |
262 | 270 | } | ... | ... |
... | ... | @@ -38,5 +38,9 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> { |
38 | 38 | |
39 | 39 | DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); |
40 | 40 | |
41 | + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); | |
42 | + | |
43 | + DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); | |
44 | + | |
41 | 45 | DeviceProfile findByName(TenantId tenantId, String profileName); |
42 | 46 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.device; | |
17 | + | |
18 | +public class DeviceProvisionServiceImpl { | |
19 | +} | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import org.springframework.data.repository.PagingAndSortingRepository; |
22 | 22 | import org.springframework.data.repository.query.Param; |
23 | 23 | import org.thingsboard.server.common.data.DeviceProfile; |
24 | 24 | import org.thingsboard.server.common.data.DeviceProfileInfo; |
25 | +import org.thingsboard.server.common.data.DeviceProfileType; | |
25 | 26 | import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; |
26 | 27 | |
27 | 28 | import java.util.UUID; |
... | ... | @@ -57,4 +58,23 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi |
57 | 58 | |
58 | 59 | DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName); |
59 | 60 | |
61 | + @Query(value = "SELECT d FROM DeviceProfileEntity d " + | |
62 | + "WHERE d.tenantId = :tenantId " + | |
63 | + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + | |
64 | + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", | |
65 | + nativeQuery = true) | |
66 | + DeviceProfileEntity findProfileByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, | |
67 | + @Param("provisionDeviceKey") String provisionDeviceKey, | |
68 | + @Param("provisionDeviceSecret") String provisionDeviceSecret); | |
69 | + | |
70 | + @Query(value = "SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + | |
71 | + " FROM DeviceProfileEntity d " + | |
72 | + "WHERE d.tenantId = :tenantId " + | |
73 | + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + | |
74 | + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", | |
75 | + nativeQuery = true) | |
76 | + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, | |
77 | + @Param("provisionDeviceKey") String provisionDeviceKey, | |
78 | + @Param("provisionDeviceSecret") String provisionDeviceSecret); | |
79 | + | |
60 | 80 | } | ... | ... |
... | ... | @@ -81,6 +81,16 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE |
81 | 81 | } |
82 | 82 | |
83 | 83 | @Override |
84 | + public DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) { | |
85 | + return DaoUtil.getData(deviceProfileRepository.findProfileByTenantIdAndProfileDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret)); | |
86 | + } | |
87 | + | |
88 | + @Override | |
89 | + public DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) { | |
90 | + return deviceProfileRepository.findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret); | |
91 | + } | |
92 | + | |
93 | + @Override | |
84 | 94 | public DeviceProfile findByName(TenantId tenantId, String profileName) { |
85 | 95 | return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName)); |
86 | 96 | } | ... | ... |