Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Scanner Core - A Modular Framework for Probe Definition, Execution, and Result Analysis.
*
* Copyright 2017-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.scanner.core.probe.result;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import de.rub.nds.scanner.core.probe.AnalyzedProperty;
import java.util.List;

/**
* Represents {@link TestResult}s which list the features for which a fault has been observed. The
* list holds the affected features only, the summary states whether any fault was found at all: an
* empty list summarizes to {@link TestResults#FALSE}, a non-empty list to {@link TestResults#TRUE}.
* An explicit TestResult can be set, for example, to communicate that the test could not be
* applied.
*
* @param <T> the type of the listed faulty features.
*/
@JsonIncludeProperties({"type", "value", "summary"})
@JsonPropertyOrder({"type", "value", "summary"})
public class FaultListResult<T> extends ListResult<T> implements SummarizableTestResult {

private final TestResults explicitSummary;

@SuppressWarnings("unused")
private FaultListResult() {
// Default constructor for deserialization
this(null, null, null);
}

/**
* Constructs a FaultListResult which summarizes the listed faulty features.
*
* @param property the analyzed property associated with this result
* @param faultyFeatures the features for which a fault has been observed, may be empty but
* should not be null
*/
public FaultListResult(AnalyzedProperty property, List<T> faultyFeatures) {
this(property, faultyFeatures, null);
}

/**
* Constructs a FaultListResult which reports the given summary instead of summarizing listed
* faulty features. Use this if the features could not be examined, e.g. {@link
* TestResults#CANNOT_BE_TESTED} if a precondition of the probe was not met.
*
* @param property the analyzed property associated with this result
* @param explicitSummary the summary to report, must not be null
*/
public FaultListResult(AnalyzedProperty property, TestResults explicitSummary) {
this(property, null, explicitSummary);
}

/**
* Constructs a FaultListResult with the specified property, list of faulty features and
* explicit summary. If the explicit summary is null, the summary is derived from the listed
* faulty features, otherwise the explicit summary takes precedence.
*
* @param property the analyzed property associated with this result
* @param faultyFeatures the features for which a fault has been observed
* @param explicitSummary the summary to report, or null to derive it from the listed features
*/
public FaultListResult(
AnalyzedProperty property, List<T> faultyFeatures, TestResults explicitSummary) {
super(property, faultyFeatures);
this.explicitSummary = explicitSummary;
}

/**
* Restores a FaultListResult from its serialized form. The analyzed property is not part of the
* serialized form and is therefore not restored.
*
* @param faultyFeatures the features for which a fault has been observed
* @param explicitSummary the name of the explicitly set summary, or null if the summary is
* derived from the listed faulty features
* @return the deserialized FaultListResult
*/
@JsonCreator
private static <T> FaultListResult<T> fromJson(
@JsonProperty("value") List<T> faultyFeatures,
@JsonProperty("summary") String explicitSummary) {
return new FaultListResult<>(
null,
faultyFeatures,
explicitSummary == null ? null : TestResults.valueOf(explicitSummary));
}

/**
* Returns the explicitly set summary of this result.
*
* @return the explicit summary, or null if the summary is derived from the listed faulty
* features
*/
@JsonGetter("summary")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public TestResults getExplicitSummary() {
return explicitSummary;
}

/**
* Summarizes the listed faults. Returns the explicit summary if one has been set. Otherwise
* returns {@link TestResults#TRUE} if at least one faulty feature has been listed, {@link
* TestResults#FALSE} if the list is empty and {@link TestResults#NOT_TESTED_YET} if no list has
* been set at all.
*
* @return the summarized TestResults value
*/
@Override
public TestResults getSummarizedResult() {
if (explicitSummary != null) {
return explicitSummary;
}
if (collection == null) {
return TestResults.NOT_TESTED_YET;
}
return TestResults.of(!collection.isEmpty());
}

/**
* Indicates whether the summary was explicitly set.
*
* @return true if an explicit summary has been set, false if the summary is derived from the
* listed faulty features
*/
@Override
@JsonIgnore
public boolean isExplicitSummary() {
return explicitSummary != null;
}

@Override
public String getName() {
return SummarizableTestResult.super.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,18 @@ public void addPropertyRatingInfluencer(PropertyResultRatingInfluencer ratingInf
}

/**
* Gets the property rating influencer for a specific test result. If no influencer is found for
* the given result, returns a new influencer with zero influence.
* Gets the property rating influencer for a specific test result. A complex result which
* summarizes to a plain {@link de.rub.nds.scanner.core.probe.result.TestResults} is matched by
* its summary. If no influencer is found for the given result, returns a new influencer with
* zero influence.
*
* @param result the test result to find an influencer for
* @return the matching property rating influencer, or a new one with zero influence if not
* found
*/
public PropertyResultRatingInfluencer getPropertyRatingInfluencer(TestResult result) {
for (PropertyResultRatingInfluencer ri : propertyRatingInfluencers) {
if (ri.getResult().equalsExpectedResult(result)) {
if (ResultMatcher.matches(result, ri.getResult())) {
return ri;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,17 @@ public void setPropertyRecommendations(
}

/**
* Gets the recommendation for a specific test result. If no recommendation is found, returns a
* default recommendation with no information available message.
* Gets the recommendation for a specific test result. A complex result which summarizes to a
* plain {@link de.rub.nds.scanner.core.probe.result.TestResults} is matched by its summary. If
* no recommendation is found, returns a default recommendation with no information available
* message.
*
* @param result the test result to find a recommendation for
* @return the matching recommendation or a default recommendation if not found
*/
public PropertyResultRecommendation getPropertyResultRecommendation(TestResult result) {
for (PropertyResultRecommendation r : propertyRecommendations) {
if (r.getResult() == result) {
if (ResultMatcher.matches(result, r.getResult())) {
return r;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Scanner Core - A Modular Framework for Probe Definition, Execution, and Result Analysis.
*
* Copyright 2017-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.scanner.core.report.rating;

import de.rub.nds.scanner.core.probe.result.SummarizableTestResult;
import de.rub.nds.scanner.core.probe.result.TestResult;

/**
* Matches the {@link TestResult} a scan yielded for a property against the result a {@link
* RatingInfluencer} or {@link Recommendation} has been configured for.
*/
final class ResultMatcher {

private ResultMatcher() {
// Private constructor to prevent instantiation of utility class
}

/**
* Determines whether the actual result of a property matches the result a rating influencer or
* recommendation has been configured for. The comparison is performed by the actual result, as
* this may be a {@link SummarizableTestResult}) which compares against its own summary result.
*
* @param actualResult the result the scan yielded, may be null
* @param configuredResult the result the influencer or recommendation is configured for, may be
* null
* @return true if the actual result matches the configured result
*/
static boolean matches(TestResult actualResult, TestResult configuredResult) {
if (actualResult == null || configuredResult == null) {
return false;
}
try {
return actualResult.equalsExpectedResult(configuredResult);
} catch (IllegalArgumentException e) {
// The actual result is a complex result which does not know how to compare itself to
// the configured result.
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Scanner Core - A Modular Framework for Probe Definition, Execution, and Result Analysis.
*
* Copyright 2017-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.scanner.core.probe.result;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.rub.nds.scanner.core.TestAnalyzedProperty;
import java.util.List;
import org.junit.jupiter.api.Test;

class FaultListResultTest {

private static FaultListResult<String> faultsFor(List<String> faultyFeatures) {
return new FaultListResult<>(TestAnalyzedProperty.TEST_ANALYZED_PROPERTY, faultyFeatures);
}

private static FaultListResult<String> summarizedAs(TestResults explicitSummary) {
return new FaultListResult<>(TestAnalyzedProperty.TEST_ANALYZED_PROPERTY, explicitSummary);
}

@Test
void emptyListSummarizesToFalse() {
assertEquals(TestResults.FALSE, faultsFor(List.of()).getSummarizedResult());
}

@Test
void nonEmptyListSummarizesToTrue() {
assertEquals(TestResults.TRUE, faultsFor(List.of("SECP256R1")).getSummarizedResult());
}

@Test
void missingListSummarizesToNotTestedYet() {
assertEquals(TestResults.NOT_TESTED_YET, faultsFor(null).getSummarizedResult());
}

@Test
void summaryIsNotExplicit() {
assertFalse(faultsFor(List.of()).isExplicitSummary());
assertNull(faultsFor(List.of()).getExplicitSummary());
}

@Test
void explicitSummaryIsReportedAsSet() {
FaultListResult<String> cannotBeTested = summarizedAs(TestResults.CANNOT_BE_TESTED);

assertTrue(cannotBeTested.isExplicitSummary());
assertEquals(TestResults.CANNOT_BE_TESTED, cannotBeTested.getExplicitSummary());
assertEquals(TestResults.CANNOT_BE_TESTED, cannotBeTested.getSummarizedResult());
assertEquals(TestResults.CANNOT_BE_TESTED.getName(), cannotBeTested.getName());
}

@Test
void explicitSummaryCollectsNoFaults() {
assertNull(summarizedAs(TestResults.CANNOT_BE_TESTED).getList());
}

@Test
void explicitSummaryTakesPrecedenceOverListedFaults() {
FaultListResult<String> result =
new FaultListResult<>(
TestAnalyzedProperty.TEST_ANALYZED_PROPERTY,
List.of("SECP256R1"),
TestResults.ERROR_DURING_TEST);

assertTrue(result.isExplicitSummary());
assertEquals(TestResults.ERROR_DURING_TEST, result.getSummarizedResult());
assertEquals(List.of("SECP256R1"), result.getList());
}

@Test
void explicitSummaryMatchesExpectedTestResults() {
FaultListResult<String> cannotBeTested = summarizedAs(TestResults.CANNOT_BE_TESTED);

assertTrue(cannotBeTested.equalsExpectedResult(TestResults.CANNOT_BE_TESTED));
assertFalse(cannotBeTested.equalsExpectedResult(TestResults.FALSE));
assertFalse(cannotBeTested.equalsExpectedResult(TestResults.TRUE));
}

@Test
void nameIsTakenFromSummary() {
assertEquals(TestResults.TRUE.getName(), faultsFor(List.of("SECP256R1")).getName());
assertEquals(TestResults.FALSE.getName(), faultsFor(List.of()).getName());
}

@Test
void matchesExpectedTestResultsOfSummary() {
FaultListResult<String> withFaults = faultsFor(List.of("SECP256R1"));
FaultListResult<String> withoutFaults = faultsFor(List.of());

assertTrue(withFaults.equalsExpectedResult(TestResults.TRUE));
assertFalse(withFaults.equalsExpectedResult(TestResults.FALSE));
assertTrue(withoutFaults.equalsExpectedResult(TestResults.FALSE));
assertFalse(withoutFaults.equalsExpectedResult(TestResults.TRUE));
}

@Test
void listStaysAccessible() {
assertEquals(
List.of("SECP256R1", "SECP384R1"),
faultsFor(List.of("SECP256R1", "SECP384R1")).getList());
}

@Test
void roundTripKeepsFaultsAndSummary() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String json = mapper.writeValueAsString(faultsFor(List.of("SECP256R1")));

FaultListResult<?> restored = mapper.readValue(json, FaultListResult.class);

assertNotNull(restored.getList());
assertEquals(List.of("SECP256R1"), restored.getList());
assertEquals(TestResults.TRUE, restored.getSummarizedResult());
}

@Test
void roundTripKeepsExplicitSummary() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String json = mapper.writeValueAsString(summarizedAs(TestResults.CANNOT_BE_TESTED));

FaultListResult<?> restored = mapper.readValue(json, FaultListResult.class);

assertTrue(restored.isExplicitSummary());
assertEquals(TestResults.CANNOT_BE_TESTED, restored.getSummarizedResult());
assertNull(restored.getList());
}

@Test
void roundTripKeepsEmptyFaultList() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String json = mapper.writeValueAsString(faultsFor(List.of()));

FaultListResult<?> restored = mapper.readValue(json, FaultListResult.class);

assertNotNull(restored.getList());
assertTrue(restored.getList().isEmpty());
assertEquals(TestResults.FALSE, restored.getSummarizedResult());
}
}
Loading