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=========================================================
21 package org.openecomp.sdc.tosca.csar;
23 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_6_1;
24 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_7_1;
25 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA_LIMIT_VERSION_3;
26 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
27 import static org.openecomp.sdc.tosca.csar.ManifestTokenType.COMPATIBLE_SPECIFICATION_VERSIONS;
29 import com.vdurmont.semver4j.Semver;
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;
35 import org.apache.commons.lang3.StringUtils;
36 import org.openecomp.sdc.common.errors.Messages;
39 * Processes a SOL004 Manifest.
41 public class SOL004ManifestOnboarding extends AbstractOnboardingManifest {
43 private int maxAllowedMetaEntries;
46 protected void processMetadata() {
47 Optional<String> currentLine = getCurrentLine();
48 //SOL004 #4.3.2: The manifest file shall start with the package metadata
49 if (!currentLine.isPresent() || !isMetadata(currentLine.get())) {
50 reportError(Messages.MANIFEST_START_METADATA);
51 continueToProcess = false;
54 while (continueToProcess) {
55 currentLine = readNextNonEmptyLine();
56 if (!currentLine.isPresent()) {
57 continueToProcess = validateMetadata();
60 final String metadataLine = currentLine.get();
61 final String metadataEntry = readEntryName(metadataLine).orElse(null);
62 if (!isMetadataEntry(metadataEntry)) {
63 if (metadata.size() < getMaxAllowedManifestMetaEntries()) {
64 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, metadataLine);
65 continueToProcess = false;
68 continueToProcess = validateMetadata();
71 final String metadataValue = readEntryValue(metadataLine).orElse(null);
72 addToMetadata(metadataEntry, metadataValue);
73 continueToProcess = isValid();
75 readNextNonEmptyLine();
79 protected void processBody() {
80 while (continueToProcess) {
81 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
82 if (manifestTokenType == null) {
83 getCurrentLine().ifPresent(line -> reportInvalidLine());
86 switch (manifestTokenType) {
90 case NON_MANO_ARTIFACT_SETS:
91 processNonManoArtifactEntry();
97 getCurrentLine().ifPresent(line -> reportInvalidLine());
98 continueToProcess = false;
105 * Processes the {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS} entry.
107 private void processNonManoArtifactEntry() {
108 Optional<String> currentLine = readNextNonEmptyLine();
109 while (currentLine.isPresent()) {
110 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
111 if (manifestTokenType == ManifestTokenType.CMS_BEGIN) {
114 if (manifestTokenType != null) {
115 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, manifestTokenType.getToken());
116 continueToProcess = false;
119 final String nonManoKey = readCurrentEntryName().orElse(null);
120 if (nonManoKey == null) {
121 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, currentLine.get());
122 continueToProcess = false;
125 readNextNonEmptyLine();
126 final List<String> nonManoSourceList = readNonManoSourceList();
128 continueToProcess = false;
131 if (nonManoSourceList.isEmpty()) {
132 reportError(Messages.MANIFEST_EMPTY_NON_MANO_KEY, nonManoKey);
133 continueToProcess = false;
136 if (nonManoSources.get(nonManoKey) == null) {
137 nonManoSources.put(nonManoKey, nonManoSourceList);
139 nonManoSources.get(nonManoKey).addAll(nonManoSourceList);
141 currentLine = getCurrentLine();
146 * Processes {@link ManifestTokenType#SOURCE} entries in {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS}.
148 * @return A list of sources paths
150 private List<String> readNonManoSourceList() {
151 final List<String> nonManoSourceList = new ArrayList<>();
152 while (getCurrentLine().isPresent()) {
153 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
154 if (manifestTokenType != ManifestTokenType.SOURCE) {
157 final String value = readCurrentEntryValue().orElse(null);
158 if (!StringUtils.isEmpty(value)) {
159 nonManoSourceList.add(value);
161 reportError(Messages.MANIFEST_EMPTY_NON_MANO_SOURCE);
164 readNextNonEmptyLine();
166 return nonManoSourceList;
170 * Reads a manifest CMS signature.
172 private void readCmsSignature() {
173 if (cmsSignature != null) {
174 reportError(Messages.MANIFEST_SIGNATURE_DUPLICATED);
175 continueToProcess = false;
178 final StringBuilder cmsSignatureBuilder = new StringBuilder();
179 cmsSignatureBuilder.append(currentLine).append("\n");
180 Optional<String> currentLine = readNextNonEmptyLine();
181 if (!getCurrentLine().isPresent()) {
184 while (currentLine.isPresent()) {
185 if (detectLineEntry().orElse(null) == ManifestTokenType.CMS_END) {
186 cmsSignatureBuilder.append(currentLine.get());
189 cmsSignatureBuilder.append(currentLine.get()).append("\n");
190 currentLine = readNextNonEmptyLine();
192 if (currentLine.isPresent()) {
193 cmsSignature = cmsSignatureBuilder.toString();
194 readNextNonEmptyLine();
196 if (getCurrentLine().isPresent()) {
197 reportError(Messages.MANIFEST_SIGNATURE_LAST_ENTRY);
198 continueToProcess = false;
203 * Detects the current line manifest token.
205 * @return the current line manifest token.
207 private Optional<ManifestTokenType> detectLineEntry() {
208 final Optional<String> currentLine = getCurrentLine();
209 if (currentLine.isPresent()) {
210 final String line = currentLine.get();
211 final String entry = readEntryName(line).orElse(null);
213 return ManifestTokenType.parse(line);
215 return ManifestTokenType.parse(entry);
218 return Optional.empty();
222 * Validates the manifest metadata content, reporting errors found.
224 * @return {@code true} if the metadata content is valid, {@code false} otherwise.
226 private boolean validateMetadata() {
227 if (metadata.isEmpty()) {
228 reportError(Messages.MANIFEST_NO_METADATA);
231 String key = metadata.keySet().stream().filter(k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k)).findFirst().orElse(null);
232 final ManifestTokenType firstManifestEntryTokenType = ManifestTokenType.parse(key).orElse(null);
233 if (firstManifestEntryTokenType == null) {
234 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, key);
237 for (final Entry<String, String> manifestEntry : metadata.entrySet()) {
238 final ManifestTokenType manifestEntryTokenType = ManifestTokenType.parse(manifestEntry.getKey()).orElse(null);
239 if (manifestEntryTokenType == null) {
240 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, manifestEntry.getKey());
243 if ((firstManifestEntryTokenType.isMetadataVnfEntry() && !manifestEntryTokenType.isMetadataVnfEntry()) || (
244 firstManifestEntryTokenType.isMetadataPnfEntry() && !manifestEntryTokenType.isMetadataPnfEntry())) {
245 reportError(Messages.MANIFEST_METADATA_UNEXPECTED_ENTRY_TYPE);
249 if (metadata.entrySet().size() != getMaxAllowedManifestMetaEntries()) {
250 reportError(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT, getMaxAllowedManifestMetaEntries());
257 * Processes a Manifest {@link ManifestTokenType#SOURCE} entry.
259 private void processSource() {
260 final Optional<String> currentLine = getCurrentLine();
261 if (!currentLine.isPresent()) {
264 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
265 if (manifestTokenType != ManifestTokenType.SOURCE) {
268 final String sourceLine = currentLine.get();
269 final String sourcePath = readEntryValue(sourceLine).orElse(null);
270 if (sourcePath == null) {
271 reportError(Messages.MANIFEST_EXPECTED_SOURCE_PATH);
274 sources.add(sourcePath);
275 readNextNonEmptyLine();
276 readAlgorithmEntry(sourcePath);
277 readSignatureEntry(sourcePath);
281 * Processes entries {@link ManifestTokenType#ALGORITHM} and {@link ManifestTokenType#HASH} of a {@link ManifestTokenType#SOURCE} entry.
283 * @param sourcePath the source path related to the algorithm entry.
285 private void readAlgorithmEntry(final String sourcePath) {
286 Optional<String> currentLine = getCurrentLine();
287 if (!currentLine.isPresent()) {
290 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
291 if (manifestTokenType == ManifestTokenType.HASH) {
292 reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_BEFORE_HASH);
293 continueToProcess = false;
296 if (manifestTokenType != ManifestTokenType.ALGORITHM) {
299 final String algorithmLine = currentLine.get();
300 final String algorithmType = readEntryValue(algorithmLine).orElse(null);
301 if (algorithmType == null) {
302 reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_VALUE);
303 continueToProcess = false;
306 currentLine = readNextNonEmptyLine();
307 if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.HASH) {
308 reportError(Messages.MANIFEST_EXPECTED_HASH_ENTRY);
309 continueToProcess = false;
312 final String hashLine = currentLine.get();
313 final String hash = readEntryValue(hashLine).orElse(null);
315 reportError(Messages.MANIFEST_EXPECTED_HASH_VALUE);
316 continueToProcess = false;
319 sourceAndChecksumMap.put(sourcePath, new AlgorithmDigest(algorithmType, hash));
320 readNextNonEmptyLine();
324 * Processes entries {@link ManifestTokenType#SIGNATURE} and {@link ManifestTokenType#CERTIFICATE} of a {@link ManifestTokenType#SOURCE} entry.
326 * @param sourcePath the source path related to the algorithm entry.
328 private void readSignatureEntry(final String sourcePath) {
329 Optional<String> currentLine = getCurrentLine();
330 if (!currentLine.isPresent()) {
333 final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
334 if (manifestTokenType == ManifestTokenType.CERTIFICATE) {
335 reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_BEFORE_CERTIFICATE);
336 continueToProcess = false;
339 if (manifestTokenType != ManifestTokenType.SIGNATURE) {
342 final String signatureLine = currentLine.get();
343 final String signatureFile = readEntryValue(signatureLine).orElse(null);
344 if (signatureFile == null) {
345 reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_VALUE);
346 continueToProcess = false;
349 currentLine = readNextNonEmptyLine();
350 if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.CERTIFICATE) {
351 sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, null));
354 final String certLine = currentLine.get();
355 final String certFile = readEntryValue(certLine).orElse(null);
356 if (certFile == null) {
357 reportError(Messages.MANIFEST_EXPECTED_CERTIFICATE_VALUE);
358 continueToProcess = false;
361 sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, certFile));
362 readNextNonEmptyLine();
365 private int getMaxAllowedManifestMetaEntries() {
366 if (maxAllowedMetaEntries == 0) {
368 metadata.containsKey(COMPATIBLE_SPECIFICATION_VERSIONS.getToken()) && !getHighestCompatibleVersion().isLowerThan(ETSI_VERSION_2_7_1);
369 //Both PNF and VNF share attribute COMPATIBLE_SPECIFICATION_VERSIONS
371 maxAllowedMetaEntries = metadata.keySet().stream().anyMatch(
372 k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k) && isMetadataEntry(k) && ManifestTokenType.parse(k).get()
373 .isMetadataPnfEntry()) ? MANIFEST_PNF_METADATA_LIMIT_VERSION_3 : MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
375 maxAllowedMetaEntries = MAX_ALLOWED_MANIFEST_META_ENTRIES;
378 return maxAllowedMetaEntries;
381 private Semver getHighestCompatibleVersion() {
382 return Arrays.asList(metadata.get(COMPATIBLE_SPECIFICATION_VERSIONS.getToken()).split(",")).stream().map(Semver::new)
383 .max((v1, v2) -> v1.compareTo(v2)).orElse(new Semver(ETSI_VERSION_2_6_1));