Commit 93b0cfec2ffd8073f8dada46928640d03100e179

Authored by Igor Kulikov
Committed by GitHub
2 parents c8158621 2d7ae47f

Merge pull request #4869 from AndrewVolosytnykhThingsboard/ota-service-url-validation

Url validation for Ota Package
@@ -32,12 +32,13 @@ import org.springframework.web.multipart.MultipartFile; @@ -32,12 +32,13 @@ import org.springframework.web.multipart.MultipartFile;
32 import org.thingsboard.server.common.data.EntityType; 32 import org.thingsboard.server.common.data.EntityType;
33 import org.thingsboard.server.common.data.OtaPackage; 33 import org.thingsboard.server.common.data.OtaPackage;
34 import org.thingsboard.server.common.data.OtaPackageInfo; 34 import org.thingsboard.server.common.data.OtaPackageInfo;
  35 +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
35 import org.thingsboard.server.common.data.audit.ActionType; 36 import org.thingsboard.server.common.data.audit.ActionType;
36 import org.thingsboard.server.common.data.exception.ThingsboardException; 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;  
38 -import org.thingsboard.server.common.data.ota.OtaPackageType;  
39 import org.thingsboard.server.common.data.id.DeviceProfileId; 38 import org.thingsboard.server.common.data.id.DeviceProfileId;
40 import org.thingsboard.server.common.data.id.OtaPackageId; 39 import org.thingsboard.server.common.data.id.OtaPackageId;
  40 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  41 +import org.thingsboard.server.common.data.ota.OtaPackageType;
41 import org.thingsboard.server.common.data.page.PageData; 42 import org.thingsboard.server.common.data.page.PageData;
42 import org.thingsboard.server.common.data.page.PageLink; 43 import org.thingsboard.server.common.data.page.PageLink;
43 import org.thingsboard.server.queue.util.TbCoreComponent; 44 import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -109,12 +110,12 @@ public class OtaPackageController extends BaseController { @@ -109,12 +110,12 @@ public class OtaPackageController extends BaseController {
109 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") 110 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
110 @RequestMapping(value = "/otaPackage", method = RequestMethod.POST) 111 @RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
111 @ResponseBody 112 @ResponseBody
112 - public OtaPackageInfo saveOtaPackageInfo(@RequestBody OtaPackageInfo otaPackageInfo) throws ThingsboardException { 113 + public OtaPackageInfo saveOtaPackageInfo(@RequestBody SaveOtaPackageInfoRequest otaPackageInfo) throws ThingsboardException {
113 boolean created = otaPackageInfo.getId() == null; 114 boolean created = otaPackageInfo.getId() == null;
114 try { 115 try {
115 otaPackageInfo.setTenantId(getTenantId()); 116 otaPackageInfo.setTenantId(getTenantId());
116 checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE); 117 checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE);
117 - OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(otaPackageInfo); 118 + OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(otaPackageInfo), otaPackageInfo.isUsesUrl());
118 logEntityAction(savedOtaPackageInfo.getId(), savedOtaPackageInfo, 119 logEntityAction(savedOtaPackageInfo.getId(), savedOtaPackageInfo,
119 null, created ? ActionType.ADDED : ActionType.UPDATED, null); 120 null, created ? ActionType.ADDED : ActionType.UPDATED, null);
120 return savedOtaPackageInfo; 121 return savedOtaPackageInfo;
@@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil; @@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil;
27 import org.thingsboard.server.common.data.DeviceProfile; 27 import org.thingsboard.server.common.data.DeviceProfile;
28 import org.thingsboard.server.common.data.OtaPackage; 28 import org.thingsboard.server.common.data.OtaPackage;
29 import org.thingsboard.server.common.data.OtaPackageInfo; 29 import org.thingsboard.server.common.data.OtaPackageInfo;
  30 +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
30 import org.thingsboard.server.common.data.Tenant; 31 import org.thingsboard.server.common.data.Tenant;
31 import org.thingsboard.server.common.data.User; 32 import org.thingsboard.server.common.data.User;
32 import org.thingsboard.server.common.data.id.DeviceProfileId; 33 import org.thingsboard.server.common.data.id.DeviceProfileId;
@@ -92,11 +93,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -92,11 +93,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
92 93
93 @Test 94 @Test
94 public void testSaveFirmware() throws Exception { 95 public void testSaveFirmware() throws Exception {
95 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 96 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
96 firmwareInfo.setDeviceProfileId(deviceProfileId); 97 firmwareInfo.setDeviceProfileId(deviceProfileId);
97 firmwareInfo.setType(FIRMWARE); 98 firmwareInfo.setType(FIRMWARE);
98 firmwareInfo.setTitle(TITLE); 99 firmwareInfo.setTitle(TITLE);
99 firmwareInfo.setVersion(VERSION); 100 firmwareInfo.setVersion(VERSION);
  101 + firmwareInfo.setUsesUrl(false);
100 102
101 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 103 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
102 104
@@ -109,7 +111,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -109,7 +111,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
109 111
110 savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); 112 savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
111 113
112 - save(savedFirmwareInfo); 114 + save(new SaveOtaPackageInfoRequest(savedFirmwareInfo, false));
113 115
114 OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class); 116 OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class);
115 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); 117 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
@@ -117,11 +119,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -117,11 +119,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
117 119
118 @Test 120 @Test
119 public void testSaveFirmwareData() throws Exception { 121 public void testSaveFirmwareData() throws Exception {
120 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 122 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
121 firmwareInfo.setDeviceProfileId(deviceProfileId); 123 firmwareInfo.setDeviceProfileId(deviceProfileId);
122 firmwareInfo.setType(FIRMWARE); 124 firmwareInfo.setType(FIRMWARE);
123 firmwareInfo.setTitle(TITLE); 125 firmwareInfo.setTitle(TITLE);
124 firmwareInfo.setVersion(VERSION); 126 firmwareInfo.setVersion(VERSION);
  127 + firmwareInfo.setUsesUrl(false);
125 128
126 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 129 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
127 130
@@ -134,7 +137,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -134,7 +137,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
134 137
135 savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); 138 savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
136 139
137 - save(savedFirmwareInfo); 140 + save(new SaveOtaPackageInfoRequest(savedFirmwareInfo, false));
138 141
139 OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class); 142 OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class);
140 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); 143 Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
@@ -151,26 +154,31 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -151,26 +154,31 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
151 154
152 @Test 155 @Test
153 public void testUpdateFirmwareFromDifferentTenant() throws Exception { 156 public void testUpdateFirmwareFromDifferentTenant() throws Exception {
154 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 157 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
155 firmwareInfo.setDeviceProfileId(deviceProfileId); 158 firmwareInfo.setDeviceProfileId(deviceProfileId);
156 firmwareInfo.setType(FIRMWARE); 159 firmwareInfo.setType(FIRMWARE);
157 firmwareInfo.setTitle(TITLE); 160 firmwareInfo.setTitle(TITLE);
158 firmwareInfo.setVersion(VERSION); 161 firmwareInfo.setVersion(VERSION);
  162 + firmwareInfo.setUsesUrl(false);
159 163
160 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 164 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
161 165
162 loginDifferentTenant(); 166 loginDifferentTenant();
163 - doPost("/api/otaPackage", savedFirmwareInfo, OtaPackageInfo.class, status().isForbidden()); 167 + doPost("/api/otaPackage",
  168 + new SaveOtaPackageInfoRequest(savedFirmwareInfo, false),
  169 + OtaPackageInfo.class,
  170 + status().isForbidden());
164 deleteDifferentTenant(); 171 deleteDifferentTenant();
165 } 172 }
166 173
167 @Test 174 @Test
168 public void testFindFirmwareInfoById() throws Exception { 175 public void testFindFirmwareInfoById() throws Exception {
169 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 176 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
170 firmwareInfo.setDeviceProfileId(deviceProfileId); 177 firmwareInfo.setDeviceProfileId(deviceProfileId);
171 firmwareInfo.setType(FIRMWARE); 178 firmwareInfo.setType(FIRMWARE);
172 firmwareInfo.setTitle(TITLE); 179 firmwareInfo.setTitle(TITLE);
173 firmwareInfo.setVersion(VERSION); 180 firmwareInfo.setVersion(VERSION);
  181 + firmwareInfo.setUsesUrl(false);
174 182
175 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 183 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
176 184
@@ -181,11 +189,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -181,11 +189,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
181 189
182 @Test 190 @Test
183 public void testFindFirmwareById() throws Exception { 191 public void testFindFirmwareById() throws Exception {
184 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 192 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
185 firmwareInfo.setDeviceProfileId(deviceProfileId); 193 firmwareInfo.setDeviceProfileId(deviceProfileId);
186 firmwareInfo.setType(FIRMWARE); 194 firmwareInfo.setType(FIRMWARE);
187 firmwareInfo.setTitle(TITLE); 195 firmwareInfo.setTitle(TITLE);
188 firmwareInfo.setVersion(VERSION); 196 firmwareInfo.setVersion(VERSION);
  197 + firmwareInfo.setUsesUrl(false);
189 198
190 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 199 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
191 200
@@ -201,11 +210,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -201,11 +210,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
201 210
202 @Test 211 @Test
203 public void testDeleteFirmware() throws Exception { 212 public void testDeleteFirmware() throws Exception {
204 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 213 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
205 firmwareInfo.setDeviceProfileId(deviceProfileId); 214 firmwareInfo.setDeviceProfileId(deviceProfileId);
206 firmwareInfo.setType(FIRMWARE); 215 firmwareInfo.setType(FIRMWARE);
207 firmwareInfo.setTitle(TITLE); 216 firmwareInfo.setTitle(TITLE);
208 firmwareInfo.setVersion(VERSION); 217 firmwareInfo.setVersion(VERSION);
  218 + firmwareInfo.setUsesUrl(false);
209 219
210 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 220 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
211 221
@@ -220,11 +230,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -220,11 +230,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
220 public void testFindTenantFirmwares() throws Exception { 230 public void testFindTenantFirmwares() throws Exception {
221 List<OtaPackageInfo> otaPackages = new ArrayList<>(); 231 List<OtaPackageInfo> otaPackages = new ArrayList<>();
222 for (int i = 0; i < 165; i++) { 232 for (int i = 0; i < 165; i++) {
223 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 233 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
224 firmwareInfo.setDeviceProfileId(deviceProfileId); 234 firmwareInfo.setDeviceProfileId(deviceProfileId);
225 firmwareInfo.setType(FIRMWARE); 235 firmwareInfo.setType(FIRMWARE);
226 firmwareInfo.setTitle(TITLE); 236 firmwareInfo.setTitle(TITLE);
227 firmwareInfo.setVersion(VERSION + i); 237 firmwareInfo.setVersion(VERSION + i);
  238 + firmwareInfo.setUsesUrl(false);
228 239
229 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 240 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
230 241
@@ -263,11 +274,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -263,11 +274,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
263 List<OtaPackageInfo> allOtaPackages = new ArrayList<>(); 274 List<OtaPackageInfo> allOtaPackages = new ArrayList<>();
264 275
265 for (int i = 0; i < 165; i++) { 276 for (int i = 0; i < 165; i++) {
266 - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); 277 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
267 firmwareInfo.setDeviceProfileId(deviceProfileId); 278 firmwareInfo.setDeviceProfileId(deviceProfileId);
268 firmwareInfo.setType(FIRMWARE); 279 firmwareInfo.setType(FIRMWARE);
269 firmwareInfo.setTitle(TITLE); 280 firmwareInfo.setTitle(TITLE);
270 firmwareInfo.setVersion(VERSION + i); 281 firmwareInfo.setVersion(VERSION + i);
  282 + firmwareInfo.setUsesUrl(false);
271 283
272 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); 284 OtaPackageInfo savedFirmwareInfo = save(firmwareInfo);
273 285
@@ -316,8 +328,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes @@ -316,8 +328,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
316 Assert.assertEquals(allOtaPackages, allLoadedOtaPackages); 328 Assert.assertEquals(allOtaPackages, allLoadedOtaPackages);
317 } 329 }
318 330
319 -  
320 - private OtaPackageInfo save(OtaPackageInfo firmwareInfo) throws Exception { 331 + private OtaPackageInfo save(SaveOtaPackageInfoRequest firmwareInfo) throws Exception {
321 return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); 332 return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
322 } 333 }
323 334
@@ -18,11 +18,11 @@ package org.thingsboard.server.dao.ota; @@ -18,11 +18,11 @@ package org.thingsboard.server.dao.ota;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.OtaPackage; 19 import org.thingsboard.server.common.data.OtaPackage;
20 import org.thingsboard.server.common.data.OtaPackageInfo; 20 import org.thingsboard.server.common.data.OtaPackageInfo;
21 -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;  
22 -import org.thingsboard.server.common.data.ota.OtaPackageType;  
23 import org.thingsboard.server.common.data.id.DeviceProfileId; 21 import org.thingsboard.server.common.data.id.DeviceProfileId;
24 import org.thingsboard.server.common.data.id.OtaPackageId; 22 import org.thingsboard.server.common.data.id.OtaPackageId;
25 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  25 +import org.thingsboard.server.common.data.ota.OtaPackageType;
26 import org.thingsboard.server.common.data.page.PageData; 26 import org.thingsboard.server.common.data.page.PageData;
27 import org.thingsboard.server.common.data.page.PageLink; 27 import org.thingsboard.server.common.data.page.PageLink;
28 28
@@ -30,7 +30,7 @@ import java.nio.ByteBuffer; @@ -30,7 +30,7 @@ import java.nio.ByteBuffer;
30 30
31 public interface OtaPackageService { 31 public interface OtaPackageService {
32 32
33 - OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo); 33 + OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl);
34 34
35 OtaPackage saveOtaPackage(OtaPackage otaPackage); 35 OtaPackage saveOtaPackage(OtaPackage otaPackage);
36 36
  1 +/**
  2 + * Copyright © 2016-2021 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;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.NoArgsConstructor;
  21 +
  22 +@Data
  23 +@EqualsAndHashCode(callSuper = true)
  24 +@NoArgsConstructor
  25 +public class SaveOtaPackageInfoRequest extends OtaPackageInfo{
  26 + boolean usesUrl;
  27 +
  28 + public SaveOtaPackageInfoRequest(OtaPackageInfo otaPackageInfo, boolean usesUrl) {
  29 + super(otaPackageInfo);
  30 + this.usesUrl = usesUrl;
  31 + }
  32 +
  33 + public SaveOtaPackageInfoRequest(SaveOtaPackageInfoRequest saveOtaPackageInfoRequest) {
  34 + super(saveOtaPackageInfoRequest);
  35 + this.usesUrl = saveOtaPackageInfoRequest.isUsesUrl();
  36 + }
  37 +}
@@ -77,8 +77,11 @@ public class BaseOtaPackageService implements OtaPackageService { @@ -77,8 +77,11 @@ public class BaseOtaPackageService implements OtaPackageService {
77 private TbTenantProfileCache tenantProfileCache; 77 private TbTenantProfileCache tenantProfileCache;
78 78
79 @Override 79 @Override
80 - public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) { 80 + public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl) {
81 log.trace("Executing saveOtaPackageInfo [{}]", otaPackageInfo); 81 log.trace("Executing saveOtaPackageInfo [{}]", otaPackageInfo);
  82 + if(isUrl && (StringUtils.isEmpty(otaPackageInfo.getUrl()) || otaPackageInfo.getUrl().trim().length() == 0)) {
  83 + throw new DataValidationException("Ota package URL should be specified!");
  84 + }
82 otaPackageInfoValidator.validate(otaPackageInfo, OtaPackageInfo::getTenantId); 85 otaPackageInfoValidator.validate(otaPackageInfo, OtaPackageInfo::getTenantId);
83 try { 86 try {
84 OtaPackageId otaPackageId = otaPackageInfo.getId(); 87 OtaPackageId otaPackageId = otaPackageInfo.getId();
@@ -277,7 +280,9 @@ public class BaseOtaPackageService implements OtaPackageService { @@ -277,7 +280,9 @@ public class BaseOtaPackageService implements OtaPackageService {
277 throw new DataValidationException("Wrong otaPackage file!"); 280 throw new DataValidationException("Wrong otaPackage file!");
278 } 281 }
279 } else { 282 } else {
280 - //TODO: validate url 283 + if(otaPackage.getData() != null) {
  284 + throw new DataValidationException("File can't be saved if URL present!");
  285 + }
281 } 286 }
282 } 287 }
283 288
@@ -336,6 +341,10 @@ public class BaseOtaPackageService implements OtaPackageService { @@ -336,6 +341,10 @@ public class BaseOtaPackageService implements OtaPackageService {
336 if (otaPackageOld.getDataSize() != null && !otaPackageOld.getDataSize().equals(otaPackage.getDataSize())) { 341 if (otaPackageOld.getDataSize() != null && !otaPackageOld.getDataSize().equals(otaPackage.getDataSize())) {
337 throw new DataValidationException("Updating otaPackage data size is prohibited!"); 342 throw new DataValidationException("Updating otaPackage data size is prohibited!");
338 } 343 }
  344 +
  345 + if(otaPackageOld.getUrl() != null && !otaPackageOld.getUrl().equals(otaPackage.getUrl())) {
  346 + throw new DataValidationException("Updating otaPackage URL is prohibited!");
  347 + }
339 } 348 }
340 349
341 private void validateImpl(OtaPackageInfo otaPackageInfo) { 350 private void validateImpl(OtaPackageInfo otaPackageInfo) {
@@ -366,6 +375,15 @@ public class BaseOtaPackageService implements OtaPackageService { @@ -366,6 +375,15 @@ public class BaseOtaPackageService implements OtaPackageService {
366 if (StringUtils.isEmpty(otaPackageInfo.getVersion())) { 375 if (StringUtils.isEmpty(otaPackageInfo.getVersion())) {
367 throw new DataValidationException("OtaPackage version should be specified!"); 376 throw new DataValidationException("OtaPackage version should be specified!");
368 } 377 }
  378 +
  379 + if(otaPackageInfo.getTitle().length() > 255) {
  380 + throw new DataValidationException("The length of title should be equal or shorter than 255");
  381 + }
  382 +
  383 + if(otaPackageInfo.getVersion().length() > 255) {
  384 + throw new DataValidationException("The length of version should be equal or shorter than 255");
  385 + }
  386 +
369 } 387 }
370 388
371 private PaginatedRemover<TenantId, OtaPackageInfo> tenantOtaPackageRemover = 389 private PaginatedRemover<TenantId, OtaPackageInfo> tenantOtaPackageRemover =
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.dao.service; 16 package org.thingsboard.server.dao.service;
17 17
18 import com.datastax.oss.driver.api.core.uuid.Uuids; 18 import com.datastax.oss.driver.api.core.uuid.Uuids;
  19 +import org.apache.commons.lang3.RandomStringUtils;
19 import org.junit.After; 20 import org.junit.After;
20 import org.junit.Assert; 21 import org.junit.Assert;
21 import org.junit.Before; 22 import org.junit.Before;
@@ -163,7 +164,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -163,7 +164,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
163 firmware.setVersion(VERSION); 164 firmware.setVersion(VERSION);
164 firmware.setUrl(URL); 165 firmware.setUrl(URL);
165 firmware.setDataSize(0L); 166 firmware.setDataSize(0L);
166 - OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware); 167 + OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware, true);
167 168
168 Assert.assertNotNull(savedFirmware); 169 Assert.assertNotNull(savedFirmware);
169 Assert.assertNotNull(savedFirmware.getId()); 170 Assert.assertNotNull(savedFirmware.getId());
@@ -174,7 +175,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -174,7 +175,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
174 Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType()); 175 Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType());
175 176
176 savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode()); 177 savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode());
177 - otaPackageService.saveOtaPackageInfo(savedFirmware); 178 + otaPackageService.saveOtaPackageInfo(savedFirmware, true);
178 179
179 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); 180 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
180 Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle()); 181 Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle());
@@ -190,7 +191,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -190,7 +191,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
190 firmwareInfo.setType(FIRMWARE); 191 firmwareInfo.setType(FIRMWARE);
191 firmwareInfo.setTitle(TITLE); 192 firmwareInfo.setTitle(TITLE);
192 firmwareInfo.setVersion(VERSION); 193 firmwareInfo.setVersion(VERSION);
193 - OtaPackageInfo savedFirmwareInfo = otaPackageService.saveOtaPackageInfo(firmwareInfo); 194 + OtaPackageInfo savedFirmwareInfo = otaPackageService.saveOtaPackageInfo(firmwareInfo, false);
194 195
195 Assert.assertNotNull(savedFirmwareInfo); 196 Assert.assertNotNull(savedFirmwareInfo);
196 Assert.assertNotNull(savedFirmwareInfo.getId()); 197 Assert.assertNotNull(savedFirmwareInfo.getId());
@@ -216,7 +217,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -216,7 +217,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
216 217
217 savedFirmwareInfo = otaPackageService.findOtaPackageInfoById(tenantId, savedFirmwareInfo.getId()); 218 savedFirmwareInfo = otaPackageService.findOtaPackageInfoById(tenantId, savedFirmwareInfo.getId());
218 savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); 219 savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
219 - otaPackageService.saveOtaPackageInfo(savedFirmwareInfo); 220 + otaPackageService.saveOtaPackageInfo(savedFirmwareInfo, false);
220 221
221 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, firmware.getId()); 222 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, firmware.getId());
222 firmware.setAdditionalInfo(JacksonUtil.newObjectNode()); 223 firmware.setAdditionalInfo(JacksonUtil.newObjectNode());
@@ -399,7 +400,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -399,7 +400,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
399 firmwareInfo.setType(FIRMWARE); 400 firmwareInfo.setType(FIRMWARE);
400 firmwareInfo.setTitle(TITLE); 401 firmwareInfo.setTitle(TITLE);
401 firmwareInfo.setVersion(VERSION); 402 firmwareInfo.setVersion(VERSION);
402 - otaPackageService.saveOtaPackageInfo(firmwareInfo); 403 + otaPackageService.saveOtaPackageInfo(firmwareInfo, false);
403 404
404 OtaPackageInfo newFirmwareInfo = new OtaPackageInfo(); 405 OtaPackageInfo newFirmwareInfo = new OtaPackageInfo();
405 newFirmwareInfo.setTenantId(tenantId); 406 newFirmwareInfo.setTenantId(tenantId);
@@ -410,7 +411,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -410,7 +411,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
410 411
411 thrown.expect(DataValidationException.class); 412 thrown.expect(DataValidationException.class);
412 thrown.expectMessage("OtaPackage with such title and version already exists!"); 413 thrown.expectMessage("OtaPackage with such title and version already exists!");
413 - otaPackageService.saveOtaPackageInfo(newFirmwareInfo); 414 + otaPackageService.saveOtaPackageInfo(newFirmwareInfo, false);
414 } 415 }
415 416
416 @Test 417 @Test
@@ -506,7 +507,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -506,7 +507,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
506 firmware.setType(FIRMWARE); 507 firmware.setType(FIRMWARE);
507 firmware.setTitle(TITLE); 508 firmware.setTitle(TITLE);
508 firmware.setVersion(VERSION); 509 firmware.setVersion(VERSION);
509 - OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware); 510 + OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware, false);
510 511
511 OtaPackageInfo foundFirmware = otaPackageService.findOtaPackageInfoById(tenantId, savedFirmware.getId()); 512 OtaPackageInfo foundFirmware = otaPackageService.findOtaPackageInfoById(tenantId, savedFirmware.getId());
512 Assert.assertNotNull(foundFirmware); 513 Assert.assertNotNull(foundFirmware);
@@ -543,7 +544,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -543,7 +544,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
543 firmwareWithUrl.setUrl(URL); 544 firmwareWithUrl.setUrl(URL);
544 firmwareWithUrl.setDataSize(0L); 545 firmwareWithUrl.setDataSize(0L);
545 546
546 - OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl); 547 + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl, true);
547 savedFwWithUrl.setHasData(true); 548 savedFwWithUrl.setHasData(true);
548 549
549 firmwares.add(savedFwWithUrl); 550 firmwares.add(savedFwWithUrl);
@@ -588,7 +589,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -588,7 +589,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
588 firmwareWithUrl.setUrl(URL); 589 firmwareWithUrl.setUrl(URL);
589 firmwareWithUrl.setDataSize(0L); 590 firmwareWithUrl.setDataSize(0L);
590 591
591 - OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl); 592 + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl, true);
592 savedFwWithUrl.setHasData(true); 593 savedFwWithUrl.setHasData(true);
593 594
594 firmwares.add(savedFwWithUrl); 595 firmwares.add(savedFwWithUrl);
@@ -627,6 +628,71 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -627,6 +628,71 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
627 Assert.assertTrue(pageData.getData().isEmpty()); 628 Assert.assertTrue(pageData.getData().isEmpty());
628 } 629 }
629 630
  631 + @Test
  632 + public void testSaveOtaPackageInfoWithBlankAndEmptyUrl() {
  633 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
  634 + firmwareInfo.setDeviceProfileId(deviceProfileId);
  635 + firmwareInfo.setType(FIRMWARE);
  636 + firmwareInfo.setTitle(TITLE);
  637 + firmwareInfo.setVersion(VERSION);
  638 + firmwareInfo.setUrl(" ");
  639 + thrown.expect(DataValidationException.class);
  640 + thrown.expectMessage("Ota package URL should be specified!");
  641 + otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
  642 + firmwareInfo.setUrl("");
  643 + otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
  644 + }
  645 +
  646 + @Test
  647 + public void testSaveOtaPackageUrlCantBeUpdated() {
  648 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
  649 + firmwareInfo.setDeviceProfileId(deviceProfileId);
  650 + firmwareInfo.setType(FIRMWARE);
  651 + firmwareInfo.setTitle(TITLE);
  652 + firmwareInfo.setVersion(VERSION);
  653 + firmwareInfo.setUrl(URL);
  654 + firmwareInfo.setTenantId(tenantId);
  655 +
  656 + OtaPackageInfo savedFirmwareInfo = otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
  657 +
  658 + thrown.expect(DataValidationException.class);
  659 + thrown.expectMessage("Updating otaPackage URL is prohibited!");
  660 +
  661 + savedFirmwareInfo.setUrl("https://newurl.com");
  662 + otaPackageService.saveOtaPackageInfo(savedFirmwareInfo, true);
  663 + }
  664 +
  665 + @Test
  666 + public void testSaveOtaPackageCantViolateSizeOfTitle() {
  667 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
  668 + firmwareInfo.setDeviceProfileId(deviceProfileId);
  669 + firmwareInfo.setType(FIRMWARE);
  670 + firmwareInfo.setTitle(RandomStringUtils.random(257));
  671 + firmwareInfo.setVersion(VERSION);
  672 + firmwareInfo.setUrl(URL);
  673 + firmwareInfo.setTenantId(tenantId);
  674 +
  675 + thrown.expect(DataValidationException.class);
  676 + thrown.expectMessage("The length of title should be equal or shorter than 255");
  677 +
  678 + otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
  679 + }
  680 +
  681 + @Test
  682 + public void testSaveOtaPackageCantViolateSizeOfVersion() {
  683 + OtaPackageInfo firmwareInfo = new OtaPackageInfo();
  684 + firmwareInfo.setDeviceProfileId(deviceProfileId);
  685 + firmwareInfo.setType(FIRMWARE);
  686 + firmwareInfo.setUrl(URL);
  687 + firmwareInfo.setTenantId(tenantId);
  688 + firmwareInfo.setTitle(TITLE);
  689 +
  690 + firmwareInfo.setVersion(RandomStringUtils.random(257));
  691 + thrown.expectMessage("The length of version should be equal or shorter than 255");
  692 +
  693 + otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
  694 + }
  695 +
630 private OtaPackage createFirmware(TenantId tenantId, String version) { 696 private OtaPackage createFirmware(TenantId tenantId, String version) {
631 OtaPackage firmware = new OtaPackage(); 697 OtaPackage firmware = new OtaPackage();
632 firmware.setTenantId(tenantId); 698 firmware.setTenantId(tenantId);
@@ -642,5 +708,4 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @@ -642,5 +708,4 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
642 firmware.setDataSize(DATA_SIZE); 708 firmware.setDataSize(DATA_SIZE);
643 return otaPackageService.saveOtaPackage(firmware); 709 return otaPackageService.saveOtaPackage(firmware);
644 } 710 }
645 -  
646 } 711 }
@@ -2977,8 +2977,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { @@ -2977,8 +2977,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2977 ).getBody(); 2977 ).getBody();
2978 } 2978 }
2979 2979
2980 - public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) {  
2981 - return restTemplate.postForEntity(baseURL + "/api/otaPackage", otaPackageInfo, OtaPackageInfo.class).getBody(); 2980 + public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl) {
  2981 + Map<String, String> params = new HashMap<>();
  2982 + params.put("isUrl", Boolean.toString(isUrl));
  2983 + return restTemplate.postForEntity(baseURL + "/api/otaPackage?isUrl={isUrl}", otaPackageInfo, OtaPackageInfo.class, params).getBody();
2982 } 2984 }
2983 2985
2984 public OtaPackageInfo saveOtaPackageData(OtaPackageId otaPackageId, String checkSum, ChecksumAlgorithm checksumAlgorithm, MultipartFile file) throws Exception { 2986 public OtaPackageInfo saveOtaPackageData(OtaPackageId otaPackageId, String checkSum, ChecksumAlgorithm checksumAlgorithm, MultipartFile file) throws Exception {
@@ -93,17 +93,17 @@ @@ -93,17 +93,17 @@
93 </mat-form-field> 93 </mat-form-field>
94 <section *ngIf="isAdd"> 94 <section *ngIf="isAdd">
95 <div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div> 95 <div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div>
96 - <mat-radio-group formControlName="resource" fxLayoutGap="16px">  
97 - <mat-radio-button value="file">Upload binary file</mat-radio-button>  
98 - <mat-radio-button value="url">Use external URL</mat-radio-button> 96 + <mat-radio-group formControlName="isURL" fxLayoutGap="16px">
  97 + <mat-radio-button [value]="false">Upload binary file</mat-radio-button>
  98 + <mat-radio-button [value]="true">Use external URL</mat-radio-button>
99 </mat-radio-group> 99 </mat-radio-group>
100 </section> 100 </section>
101 - <section *ngIf="entityForm.get('resource').value === 'file'"> 101 + <section *ngIf="!entityForm.get('isURL').value">
102 <section *ngIf="isAdd"> 102 <section *ngIf="isAdd">
103 <tb-file-input 103 <tb-file-input
104 formControlName="file" 104 formControlName="file"
105 workFromFileObj="true" 105 workFromFileObj="true"
106 - [required]="entityForm.get('resource').value === 'file'" 106 + [required]="!entityForm.get('isURL').value"
107 dropLabel="{{'ota-update.drop-file' | translate}}"> 107 dropLabel="{{'ota-update.drop-file' | translate}}">
108 </tb-file-input> 108 </tb-file-input>
109 <mat-checkbox formControlName="generateChecksum" style="margin-top: 16px"> 109 <mat-checkbox formControlName="generateChecksum" style="margin-top: 16px">
@@ -143,12 +143,12 @@ @@ -143,12 +143,12 @@
143 </div> 143 </div>
144 </section> 144 </section>
145 </section> 145 </section>
146 - <section *ngIf="entityForm.get('resource').value === 'url'" style="margin-top: 8px"> 146 + <section *ngIf="entityForm.get('isURL').value" style="margin-top: 8px">
147 <mat-form-field class="mat-block"> 147 <mat-form-field class="mat-block">
148 <mat-label translate>ota-update.direct-url</mat-label> 148 <mat-label translate>ota-update.direct-url</mat-label>
149 <input matInput formControlName="url" 149 <input matInput formControlName="url"
150 type="text" 150 type="text"
151 - [required]="entityForm.get('resource').value === 'url'"> 151 + [required]="entityForm.get('isURL').value">
152 <mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')" translate> 152 <mat-error *ngIf="entityForm.get('url').hasError('required') || entityForm.get('url').hasError('pattern')" translate>
153 ota-update.direct-url-required 153 ota-update.direct-url-required
154 </mat-error> 154 </mat-error>
@@ -56,11 +56,11 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O @@ -56,11 +56,11 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
56 56
57 ngOnInit() { 57 ngOnInit() {
58 super.ngOnInit(); 58 super.ngOnInit();
59 - this.entityForm.get('resource').valueChanges.pipe( 59 + this.entityForm.get('isURL').valueChanges.pipe(
60 filter(() => this.isAdd), 60 filter(() => this.isAdd),
61 takeUntil(this.destroy$) 61 takeUntil(this.destroy$)
62 - ).subscribe((resource) => {  
63 - if (resource === 'file') { 62 + ).subscribe((isURL) => {
  63 + if (isURL === false) {
64 this.entityForm.get('url').clearValidators(); 64 this.entityForm.get('url').clearValidators();
65 this.entityForm.get('file').setValidators(Validators.required); 65 this.entityForm.get('file').setValidators(Validators.required);
66 this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); 66 this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
@@ -97,7 +97,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O @@ -97,7 +97,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
97 checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], 97 checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256],
98 checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], 98 checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)],
99 url: [entity ? entity.url : ''], 99 url: [entity ? entity.url : ''],
100 - resource: ['file'], 100 + isURL: [false],
101 additionalInfo: this.fb.group( 101 additionalInfo: this.fb.group(
102 { 102 {
103 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], 103 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
@@ -127,7 +127,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O @@ -127,7 +127,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
127 dataSize: entity.dataSize, 127 dataSize: entity.dataSize,
128 contentType: entity.contentType, 128 contentType: entity.contentType,
129 url: entity.url, 129 url: entity.url,
130 - resource: isNotEmptyStr(entity.url) ? 'url' : 'file', 130 + isURL: isNotEmptyStr(entity.url),
131 additionalInfo: { 131 additionalInfo: {
132 description: entity.additionalInfo ? entity.additionalInfo.description : '' 132 description: entity.additionalInfo ? entity.additionalInfo.description : ''
133 } 133 }
@@ -172,12 +172,11 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O @@ -172,12 +172,11 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
172 } 172 }
173 173
174 prepareFormValue(formValue: any): any { 174 prepareFormValue(formValue: any): any {
175 - if (formValue.resource === 'url') { 175 + if (formValue.isURL) {
176 delete formValue.file; 176 delete formValue.file;
177 } else { 177 } else {
178 delete formValue.url; 178 delete formValue.url;
179 } 179 }
180 - delete formValue.resource;  
181 delete formValue.generateChecksum; 180 delete formValue.generateChecksum;
182 return super.prepareFormValue(formValue); 181 return super.prepareFormValue(formValue);
183 } 182 }
@@ -99,6 +99,7 @@ export interface OtaPackageInfo extends BaseData<OtaPackageId> { @@ -99,6 +99,7 @@ export interface OtaPackageInfo extends BaseData<OtaPackageId> {
99 contentType: string; 99 contentType: string;
100 dataSize?: number; 100 dataSize?: number;
101 additionalInfo?: any; 101 additionalInfo?: any;
  102 + isURL?: boolean;
102 } 103 }
103 104
104 export interface OtaPackage extends OtaPackageInfo { 105 export interface OtaPackage extends OtaPackageInfo {