2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2019 Nordix Foundation.
4 * Modification Copyright (C) 2021 Nokia.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.openecomp.sdc.tosca.csar;
24 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_6_1;
25 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_7_1;
26 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA_LIMIT_VERSION_3;
27 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
28 import static org.openecomp.sdc.tosca.csar.ManifestTokenType.COMPATIBLE_SPECIFICATION_VERSIONS;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.Optional;
36 import org.apache.commons.lang.StringUtils;
37 import org.openecomp.sdc.common.errors.Messages;
39 import com.vdurmont.semver4j.Semver;
42 * Processes a SOL004 Manifest.
44 public class SOL004ManifestOnboarding extends AbstractOnboardingManifest {
46 private int maxAllowedMetaEntries;
49 protected void processMetadata() {
50 Optional<String> currentLine = getCurrentLine();
51 //SOL004 #4.3.2: The manifest file shall start with the package metadata
52 if (!currentLine.isPresent() || !isMetadata(currentLine.get())) {
53 reportError(Messages.MANIFEST_START_METADATA);
54 continueToProcess = false;
57 while (continueToProcess) {
58 currentLine = readNextNonEmptyLine();
59 if (!currentLine.isPresent()) {
60 continueToProcess = validateMetadata();
63 final String metadataLine = currentLine.get();
64 final String metadataEntry = readEntryName(metadataLine).orElse(null);
65 if (!isMetadataEntry(metadataEntry)) {
66 if (metadata.size() < getMaxAllowedManifestMetaEntries()) {
67 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, metadataLine);
68 continueToProcess = false;
71 continueToProcess = validateMetadata();
74 final String metadataValue = readEntryValue(metadataLine).orElse(null);
75 addToMetadata(metadataEntry, metadataValue);
76 continueToProcess = isValid();
78 readNextNonEmptyLine();
82 protected void processBody() {
83 while (continueToProcess) {
84 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
85 if (manifestTokenType == null) {
86 getCurrentLine().ifPresent(line -> reportInvalidLine());
90 switch (manifestTokenType) {
94 case NON_MANO_ARTIFACT_SETS:
95 processNonManoArtifactEntry();
101 getCurrentLine().ifPresent(line -> reportInvalidLine());
102 continueToProcess = false;
109 * Processes the {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS} entry.
111 private void processNonManoArtifactEntry() {
112 Optional<String> currentLine = readNextNonEmptyLine();
113 while (currentLine.isPresent()) {
114 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
115 if (manifestTokenType == ManifestTokenType.CMS_BEGIN) {
118 if (manifestTokenType != null) {
119 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, manifestTokenType.getToken());
120 continueToProcess = false;
123 final String nonManoKey = readCurrentEntryName().orElse(null);
124 if (nonManoKey == null) {
125 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, currentLine.get());
126 continueToProcess = false;
129 readNextNonEmptyLine();
130 final List<String> nonManoSourceList = readNonManoSourceList();
132 continueToProcess = false;
135 if (nonManoSourceList.isEmpty()) {
136 reportError(Messages.MANIFEST_EMPTY_NON_MANO_KEY, nonManoKey);
137 continueToProcess = false;
140 if (nonManoSources.get(nonManoKey) == null) {
141 nonManoSources.put(nonManoKey, nonManoSourceList);
143 nonManoSources.get(nonManoKey).addAll(nonManoSourceList);
145 currentLine = getCurrentLine();
150 * Processes {@link ManifestTokenType#SOURCE} entries in {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS}.
152 * @return A list of sources paths
154 private List<String> readNonManoSourceList() {
155 final List<String> nonManoSourceList = new ArrayList<>();
156 while (getCurrentLine().isPresent()) {
157 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
158 if (manifestTokenType != ManifestTokenType.SOURCE) {
162 final String value = readCurrentEntryValue().orElse(null);
163 if (!StringUtils.isEmpty(value)) {
164 nonManoSourceList.add(value);
166 reportError(Messages.MANIFEST_EMPTY_NON_MANO_SOURCE);
170 readNextNonEmptyLine();
172 return nonManoSourceList;
176 * Reads a manifest CMS signature.
178 private void readCmsSignature() {
179 if (cmsSignature != null) {
180 reportError(Messages.MANIFEST_SIGNATURE_DUPLICATED);
181 continueToProcess = false;
184 final StringBuilder cmsSignatureBuilder = new StringBuilder();
186 cmsSignatureBuilder.append(currentLine).append("\n");
187 Optional<String> currentLine = readNextNonEmptyLine();
188 if(!getCurrentLine().isPresent()) {
191 while (currentLine.isPresent()) {
192 if (detectLineEntry().orElse(null) == ManifestTokenType.CMS_END) {
193 cmsSignatureBuilder.append(currentLine.get());
196 cmsSignatureBuilder.append(currentLine.get()).append("\n");
197 currentLine = readNextNonEmptyLine();
200 if (currentLine.isPresent()) {
201 cmsSignature = cmsSignatureBuilder.toString();
202 readNextNonEmptyLine();
205 if (getCurrentLine().isPresent()) {
206 reportError(Messages.MANIFEST_SIGNATURE_LAST_ENTRY);
207 continueToProcess = false;
212 * Detects the current line manifest token.
214 * @return the current line manifest token.
216 private Optional<ManifestTokenType> detectLineEntry() {
217 final Optional<String> currentLine = getCurrentLine();
218 if (currentLine.isPresent()) {
219 final String line = currentLine.get();
220 final String entry = readEntryName(line).orElse(null);
222 return ManifestTokenType.parse(line);
224 return ManifestTokenType.parse(entry);
227 return Optional.empty();
231 * Validates the manifest metadata content, reporting errors found.
233 * @return {@code true} if the metadata content is valid, {@code false} otherwise.
235 private boolean validateMetadata() {
236 if (metadata.isEmpty()) {
237 reportError(Messages.MANIFEST_NO_METADATA);
240 String key = metadata.keySet().stream().filter(k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k))
241 .findFirst().orElse(null);
242 final ManifestTokenType firstManifestEntryTokenType = ManifestTokenType.parse(key).orElse(null);
243 if (firstManifestEntryTokenType == null) {
244 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, key);
247 for (final Entry<String, String> manifestEntry : metadata.entrySet()) {
248 final ManifestTokenType manifestEntryTokenType = ManifestTokenType.parse(manifestEntry.getKey())
250 if (manifestEntryTokenType == null) {
251 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, manifestEntry.getKey());
254 if ((firstManifestEntryTokenType.isMetadataVnfEntry() && !manifestEntryTokenType.isMetadataVnfEntry())
255 || (firstManifestEntryTokenType.isMetadataPnfEntry() && !manifestEntryTokenType.isMetadataPnfEntry())) {
256 reportError(Messages.MANIFEST_METADATA_UNEXPECTED_ENTRY_TYPE);
261 if (metadata.entrySet().size() != getMaxAllowedManifestMetaEntries()) {
262 reportError(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT, getMaxAllowedManifestMetaEntries());
270 * Processes a Manifest {@link ManifestTokenType#SOURCE} entry.
272 private void processSource() {
273 final Optional<String> currentLine = getCurrentLine();
274 if (!currentLine.isPresent()) {
277 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
278 if (manifestTokenType != ManifestTokenType.SOURCE) {
282 final String sourceLine = currentLine.get();
283 final String sourcePath = readEntryValue(sourceLine).orElse(null);
285 if (sourcePath == null) {
286 reportError(Messages.MANIFEST_EXPECTED_SOURCE_PATH);
289 sources.add(sourcePath);
290 readNextNonEmptyLine();
291 readAlgorithmEntry(sourcePath);
292 readSignatureEntry(sourcePath);
296 * Processes entries {@link ManifestTokenType#ALGORITHM} and {@link ManifestTokenType#HASH} of a {@link
297 * ManifestTokenType#SOURCE} entry.
299 * @param sourcePath the source path related to the algorithm entry.
301 private void readAlgorithmEntry(final String sourcePath) {
302 Optional<String> currentLine = getCurrentLine();
303 if (!currentLine.isPresent()) {
306 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
307 if (manifestTokenType == ManifestTokenType.HASH) {
308 reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_BEFORE_HASH);
309 continueToProcess = false;
312 if (manifestTokenType != ManifestTokenType.ALGORITHM) {
315 final String algorithmLine = currentLine.get();
316 final String algorithmType = readEntryValue(algorithmLine).orElse(null);
317 if (algorithmType == null) {
318 reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_VALUE);
319 continueToProcess = false;
323 currentLine = readNextNonEmptyLine();
324 if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.HASH) {
325 reportError(Messages.MANIFEST_EXPECTED_HASH_ENTRY);
326 continueToProcess = false;
330 final String hashLine = currentLine.get();
331 final String hash = readEntryValue(hashLine).orElse(null);
333 reportError(Messages.MANIFEST_EXPECTED_HASH_VALUE);
334 continueToProcess = false;
337 sourceAndChecksumMap.put(sourcePath, new AlgorithmDigest(algorithmType, hash));
338 readNextNonEmptyLine();
342 * Processes entries {@link ManifestTokenType#SIGNATURE} and {@link ManifestTokenType#CERTIFICATE} of a {@link
343 * ManifestTokenType#SOURCE} entry.
345 * @param sourcePath the source path related to the algorithm entry.
347 private void readSignatureEntry(final String sourcePath) {
348 Optional<String> currentLine = getCurrentLine();
349 if (!currentLine.isPresent()) {
352 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
353 if (manifestTokenType == ManifestTokenType.CERTIFICATE) {
354 reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_BEFORE_CERTIFICATE);
355 continueToProcess = false;
358 if (manifestTokenType != ManifestTokenType.SIGNATURE) {
361 final String signatureLine = currentLine.get();
362 final String signatureFile = readEntryValue(signatureLine).orElse(null);
363 if (signatureFile == null) {
364 reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_VALUE);
365 continueToProcess = false;
369 currentLine = readNextNonEmptyLine();
370 if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.CERTIFICATE) {
371 sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, null));
375 final String certLine = currentLine.get();
376 final String certFile = readEntryValue(certLine).orElse(null);
377 if (certFile == null) {
378 reportError(Messages.MANIFEST_EXPECTED_CERTIFICATE_VALUE);
379 continueToProcess = false;
382 sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, certFile));
383 readNextNonEmptyLine();
386 private int getMaxAllowedManifestMetaEntries() {
387 if (maxAllowedMetaEntries == 0) {
388 boolean isVersion3 = metadata.containsKey(COMPATIBLE_SPECIFICATION_VERSIONS.getToken())
389 && !getHighestCompatibleVersion().isLowerThan(ETSI_VERSION_2_7_1);
390 //Both PNF and VNF share attribute COMPATIBLE_SPECIFICATION_VERSIONS
392 maxAllowedMetaEntries = metadata.keySet().stream()
393 .anyMatch(k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k)
394 && isMetadataEntry(k) && ManifestTokenType.parse(k).get().isMetadataPnfEntry())
395 ? MANIFEST_PNF_METADATA_LIMIT_VERSION_3 : MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
397 maxAllowedMetaEntries = MAX_ALLOWED_MANIFEST_META_ENTRIES;
399 return maxAllowedMetaEntries;
402 private Semver getHighestCompatibleVersion() {
403 return Arrays.asList(metadata.get(COMPATIBLE_SPECIFICATION_VERSIONS.getToken()).split(","))
404 .stream().map(Semver::new).max((v1, v2) -> v1.compareTo(v2))
405 .orElse(new Semver(ETSI_VERSION_2_6_1));