AttackStep.java
/*
* Copyright 2020-2022 Foreseeti AB <https://foreseeti.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mal_lang.langspec;
import static java.util.Objects.requireNonNull;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.mal_lang.langspec.builders.AttackStepBuilder;
import org.mal_lang.langspec.step.StepExpression;
import org.mal_lang.langspec.ttc.TtcExpression;
/**
* Immutable class representing an attack step of an asset in a MAL language.
*
* @since 1.0.0
*/
public final class AttackStep {
private final String name;
private final Meta meta;
private final Asset asset;
private final AttackStepType type;
private final Set<String> tags;
private final Risk risk;
private final TtcExpression ttc;
private final Steps requires;
private final Steps reaches;
private AttackStep(
String name,
Meta meta,
Asset asset,
AttackStepType type,
List<String> tags,
Risk risk,
TtcExpression ttc,
Steps requires,
Steps reaches) {
this.name = requireNonNull(name);
this.meta = requireNonNull(meta);
this.asset = requireNonNull(asset);
this.type = requireNonNull(type);
this.tags = new LinkedHashSet<>(requireNonNull(tags));
this.risk = risk;
this.ttc = ttc;
this.requires = requires;
this.reaches = reaches;
}
/**
* Returns the name of this {@code AttackStep} object.
*
* @return the name of this {@code AttackStep} object
* @since 1.0.0
*/
public String getName() {
return this.name;
}
/**
* Returns the meta info of this {@code AttackStep} object.
*
* @return the meta info of this {@code AttackStep} object
* @since 1.0.0
*/
public Meta getMeta() {
return this.meta;
}
/**
* Returns the asset of this {@code AttackStep} object.
*
* @return the asset of this {@code AttackStep} object
* @since 1.0.0
*/
public Asset getAsset() {
return this.asset;
}
/**
* Returns the type of this {@code AttackStep} object.
*
* @return the type of this {@code AttackStep} object
* @since 1.0.0
*/
public AttackStepType getType() {
return this.type;
}
/**
* Returns whether {@code name} is the name of a local tag in this {@code AttackStep} object.
*
* @param name the name of the local tag
* @return whether {@code name} is the name of a local tag in this {@code AttackStep} object
* @throws java.lang.NullPointerException if {@code name} is {@code null}
* @since 1.0.0
*/
public boolean hasLocalTag(String name) {
return this.tags.contains(requireNonNull(name));
}
/**
* Returns a list of all local tags in this {@code AttackStep} object.
*
* @return a list of all local tags in this {@code AttackStep} object
* @since 1.0.0
*/
public List<String> getLocalTags() {
return List.copyOf(this.tags);
}
/**
* Returns whether {@code name} is the name of a tag in this {@code AttackStep} object.
*
* @param name the name of the tag
* @return whether {@code name} is the name of a tag in this {@code AttackStep} object
* @throws java.lang.NullPointerException if {@code name} is {@code null}
* @since 1.0.0
*/
public boolean hasTag(String name) {
return this.hasLocalTag(name)
|| this.hasSuperAttackStep() && this.getSuperAttackStep().hasTag(name);
}
/**
* Returns a list of all tags in this {@code AttackStep} object.
*
* @return a list of all tags in this {@code AttackStep} object
* @since 1.0.0
*/
public List<String> getTags() {
return List.copyOf(this.getTagsSet());
}
private Set<String> getTagsSet() {
var tagsSet =
this.hasSuperAttackStep()
? this.getSuperAttackStep().getTagsSet()
: new LinkedHashSet<String>();
tagsSet.addAll(this.tags);
return tagsSet;
}
/**
* Returns whether this {@code AttackStep} object has a local risk.
*
* @return whether this {@code AttackStep} object has a local risk
* @since 1.0.0
*/
public boolean hasLocalRisk() {
return this.risk != null;
}
/**
* Returns the local risk of this {@code AttackStep} object.
*
* @return the local risk of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* a local risk
* @since 1.0.0
*/
public Risk getLocalRisk() {
if (!this.hasLocalRisk()) {
throw new UnsupportedOperationException("Local risk not found");
}
return this.risk;
}
/**
* Returns whether this {@code AttackStep} object has a risk.
*
* @return whether this {@code AttackStep} object has a risk
* @since 1.0.0
*/
public boolean hasRisk() {
return this.hasLocalRisk() || this.hasSuperAttackStep() && this.getSuperAttackStep().hasRisk();
}
/**
* Returns the risk of this {@code AttackStep} object.
*
* @return the risk of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* a risk
* @since 1.0.0
*/
public Risk getRisk() {
if (!this.hasRisk()) {
throw new UnsupportedOperationException("Risk not found");
}
return this.hasLocalRisk() ? this.getLocalRisk() : this.getSuperAttackStep().getRisk();
}
/**
* Returns whether this {@code AttackStep} object has a local TTC.
*
* @return whether this {@code AttackStep} object has a local TTC
* @since 1.0.0
*/
public boolean hasLocalTtc() {
return this.ttc != null;
}
/**
* Returns the local TTC of this {@code AttackStep} object.
*
* @return the local TTC of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* a local TTC
* @since 1.0.0
*/
public TtcExpression getLocalTtc() {
if (!this.hasLocalTtc()) {
throw new UnsupportedOperationException("Local TTC not found");
}
return this.ttc;
}
/**
* Returns whether this {@code AttackStep} object has a TTC.
*
* @return whether this {@code AttackStep} object has a TTC
* @since 1.0.0
*/
public boolean hasTtc() {
return this.hasLocalTtc() || this.hasSuperAttackStep() && this.getSuperAttackStep().hasTtc();
}
/**
* Returns the TTC of this {@code AttackStep} object.
*
* @return the TTC of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* a TTC
* @since 1.0.0
*/
public TtcExpression getTtc() {
if (!this.hasTtc()) {
throw new UnsupportedOperationException("TTC not found");
}
return this.hasLocalTtc() ? this.getLocalTtc() : this.getSuperAttackStep().getTtc();
}
/**
* Returns whether this {@code AttackStep} object has local requires steps.
*
* @return whether this {@code AttackStep} object has local requires steps
* @since 1.0.0
*/
public boolean hasLocalRequires() {
return this.requires != null;
}
/**
* Returns the local requires steps of this {@code AttackStep} object.
*
* @return the local requires steps of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* local requires steps
* @since 1.0.0
*/
public Steps getLocalRequires() {
if (!this.hasLocalRequires()) {
throw new UnsupportedOperationException("Local requires not found");
}
return this.requires;
}
/**
* Returns the requires steps of this {@code AttackStep} object.
*
* @return the requires steps of this {@code AttackStep} object
* @since 1.0.0
*/
public List<StepExpression> getRequires() {
return List.copyOf(this.getRequiresList());
}
private List<StepExpression> getRequiresList() {
var overrides = this.hasLocalRequires() && this.requires.overrides();
var requiresList =
this.hasSuperAttackStep() && !overrides
? this.getSuperAttackStep().getRequiresList()
: new ArrayList<StepExpression>();
if (this.hasLocalRequires()) {
requiresList.addAll(this.requires.getStepExpressions());
}
return requiresList;
}
/**
* Returns whether this {@code AttackStep} object has local reaches steps.
*
* @return whether this {@code AttackStep} object has local reaches steps
* @since 1.0.0
*/
public boolean hasLocalReaches() {
return this.reaches != null;
}
/**
* Returns the local reaches steps of this {@code AttackStep} object.
*
* @return the local reaches steps of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* local reaches steps
* @since 1.0.0
*/
public Steps getLocalReaches() {
if (!this.hasLocalReaches()) {
throw new UnsupportedOperationException("Local reaches not found");
}
return this.reaches;
}
/**
* Returns the reaches steps of this {@code AttackStep} object.
*
* @return the reaches steps of this {@code AttackStep} object
* @since 1.0.0
*/
public List<StepExpression> getReaches() {
return List.copyOf(this.getReachesList());
}
private List<StepExpression> getReachesList() {
var overrides = this.hasLocalReaches() && this.reaches.overrides();
var reachesList =
this.hasSuperAttackStep() && !overrides
? this.getSuperAttackStep().getReachesList()
: new ArrayList<StepExpression>();
if (this.hasLocalReaches()) {
reachesList.addAll(this.reaches.getStepExpressions());
}
return reachesList;
}
/**
* Returns whether this {@code AttackStep} object has a super attack step.
*
* @return whether this {@code AttackStep} object has a super attack step
* @since 1.0.0
*/
public boolean hasSuperAttackStep() {
return this.asset.hasSuperAsset() && this.asset.getSuperAsset().hasAttackStep(this.name);
}
/**
* Returns the super attack step of this {@code AttackStep} object.
*
* @return the super attack step of this {@code AttackStep} object
* @throws java.lang.UnsupportedOperationException if this {@code AttackStep} object does not have
* a super attack step
* @since 1.0.0
*/
public AttackStep getSuperAttackStep() {
if (!this.hasSuperAttackStep()) {
throw new UnsupportedOperationException("Super attack step not found");
}
return this.asset.getSuperAsset().getAttackStep(this.name);
}
JsonObject toJson() {
var jsonTags = Json.createArrayBuilder();
for (var tag : this.tags) {
jsonTags.add(tag);
}
var jsonAttackStep =
Json.createObjectBuilder()
.add("name", this.name)
.add("meta", this.meta.toJson())
.add("type", this.type.toString())
.add("tags", jsonTags);
if (this.risk == null) {
jsonAttackStep.addNull("risk");
} else {
jsonAttackStep.add("risk", this.risk.toJson());
}
if (this.ttc == null) {
jsonAttackStep.addNull("ttc");
} else {
jsonAttackStep.add("ttc", this.ttc.toJson());
}
if (this.requires == null) {
jsonAttackStep.addNull("requires");
} else {
jsonAttackStep.add("requires", this.requires.toJson());
}
if (this.reaches == null) {
jsonAttackStep.addNull("reaches");
} else {
jsonAttackStep.add("reaches", this.reaches.toJson());
}
return jsonAttackStep.build();
}
static AttackStep fromBuilder(AttackStepBuilder builder, Asset asset) {
requireNonNull(builder);
requireNonNull(asset);
var requires = builder.getRequires() == null ? null : Steps.fromBuilder(builder.getRequires());
var reaches = builder.getReaches() == null ? null : Steps.fromBuilder(builder.getReaches());
return new AttackStep(
builder.getName(),
Meta.fromBuilder(builder.getMeta()),
asset,
builder.getType(),
builder.getTags(),
builder.getRisk(),
builder.getTtc(),
requires,
reaches);
}
}