From 2922bc5bd6ce635cae80fc6409b395991d019531 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 28 Jun 2023 19:28:05 +0200 Subject: [PATCH] Port wot-dijkstra Java classes to Kotlin --- build.gradle | 2 +- .../pgpainless/wot/CertificationFactory.java | 9 +- .../java/org/pgpainless/wot/WebOfTrust.java | 7 +- .../pgpainless/wot/dijkstra/IntegerUtils.java | 24 --- .../wot/dijkstra/sq/Certification.java | 148 ----------------- .../wot/dijkstra/sq/CertificationSet.java | 127 -------------- .../org/pgpainless/wot/dijkstra/sq/Depth.java | 82 --------- .../pgpainless/wot/dijkstra/sq/Network.java | 156 ------------------ .../pgpainless/wot/dijkstra/sq/Optional.java | 78 --------- .../org/pgpainless/wot/dijkstra/sq/Path.java | 94 ----------- .../org/pgpainless/wot/dijkstra/sq/Paths.java | 50 ------ .../pgpainless/wot/dijkstra/sq/RegexSet.java | 50 ------ .../wot/dijkstra/sq/CertSynopsis.kt | 4 + .../wot/dijkstra/sq/Certification.kt | 41 +++++ .../wot/dijkstra/sq/CertificationSet.kt | 57 +++++++ .../org/pgpainless/wot/dijkstra/sq/Depth.kt | 76 +++++++++ .../org/pgpainless/wot/dijkstra/sq/Network.kt | 57 +++++++ .../org/pgpainless/wot/dijkstra/sq/Path.kt | 80 +++++++++ .../org/pgpainless/wot/dijkstra/sq/Paths.kt | 24 +++ .../pgpainless/wot/dijkstra/sq/RegexSet.kt | 41 +++++ .../pgpainless/wot/dijkstra/sq/DepthTest.java | 12 +- 21 files changed, 395 insertions(+), 824 deletions(-) delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/IntegerUtils.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Certification.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/CertificationSet.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Depth.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Network.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Optional.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Path.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Paths.java delete mode 100644 wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/RegexSet.java create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Certification.kt create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertificationSet.kt create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Depth.kt create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Network.kt create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Path.kt create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Paths.kt create mode 100644 wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/RegexSet.kt diff --git a/build.gradle b/build.gradle index d04b6345..1d13fde6 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ allprojects { } // For library modules, enable android api compatibility check - if (it.name != 'pgpainless-cli') { + if (it.name != 'pgpainless-cli' && it.name != 'wot-dijkstra') { // animalsniffer apply plugin: 'ru.vyarus.animalsniffer' dependencies { diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/CertificationFactory.java b/pgpainless-wot/src/main/java/org/pgpainless/wot/CertificationFactory.java index e641f4ad..a13b3948 100644 --- a/pgpainless-wot/src/main/java/org/pgpainless/wot/CertificationFactory.java +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/CertificationFactory.java @@ -14,7 +14,6 @@ import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.wot.dijkstra.sq.CertSynopsis; import org.pgpainless.wot.dijkstra.sq.Certification; import org.pgpainless.wot.dijkstra.sq.Depth; -import org.pgpainless.wot.dijkstra.sq.Optional; import org.pgpainless.wot.dijkstra.sq.RegexSet; /** @@ -34,7 +33,7 @@ public class CertificationFactory { public static Certification fromDelegation(CertSynopsis issuer, CertSynopsis target, PGPSignature signature) { - return fromSignature(issuer, Optional.empty(), target, signature); + return fromSignature(issuer, null, target, signature); } /** @@ -50,7 +49,7 @@ public class CertificationFactory { String targetUserId, CertSynopsis target, PGPSignature signature) { - return fromSignature(issuer, Optional.just(targetUserId), target, signature); + return fromSignature(issuer, targetUserId, target, signature); } /** @@ -63,7 +62,7 @@ public class CertificationFactory { * @return certification */ public static Certification fromSignature(CertSynopsis issuer, - Optional targetUserId, + String targetUserId, CertSynopsis target, PGPSignature signature) { return new Certification( @@ -71,7 +70,7 @@ public class CertificationFactory { target, targetUserId, SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime(), - Optional.maybe(SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature)), + SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature), SignatureSubpacketsUtil.isExportable(signature), getTrustAmountFrom(signature), getTrustDepthFrom(signature), diff --git a/pgpainless-wot/src/main/java/org/pgpainless/wot/WebOfTrust.java b/pgpainless-wot/src/main/java/org/pgpainless/wot/WebOfTrust.java index fab2e585..971a8b28 100644 --- a/pgpainless-wot/src/main/java/org/pgpainless/wot/WebOfTrust.java +++ b/pgpainless-wot/src/main/java/org/pgpainless/wot/WebOfTrust.java @@ -33,7 +33,6 @@ import org.pgpainless.wot.dijkstra.sq.CertSynopsis; import org.pgpainless.wot.dijkstra.sq.Certification; import org.pgpainless.wot.dijkstra.sq.CertificationSet; import org.pgpainless.wot.dijkstra.sq.Network; -import org.pgpainless.wot.dijkstra.sq.Optional; import org.pgpainless.wot.dijkstra.sq.ReferenceTime; import org.pgpainless.wot.sugar.IterableIterator; import org.pgpainless.wot.sugar.PrefixedIterator; @@ -80,7 +79,7 @@ public class WebOfTrust implements CertificateAuthority { Iterator certificates = certificateStore.items(); Iterator withTrustRoot = new PrefixedIterator<>(trustRoot, certificates); IterableIterator iterable = new IterableIterator<>(withTrustRoot); - network = fromCertificates(iterable, PGPainless.getPolicy(), Optional.just(ReferenceTime.now())); + network = fromCertificates(iterable, PGPainless.getPolicy(), ReferenceTime.now()); } @@ -95,9 +94,9 @@ public class WebOfTrust implements CertificateAuthority { public static Network fromCertificates( Iterable certificates, Policy policy, - Optional optReferenceTime) { + ReferenceTime optReferenceTime) { - ReferenceTime referenceTime = optReferenceTime.isPresent() ? optReferenceTime.get() : ReferenceTime.now(); + ReferenceTime referenceTime = optReferenceTime == null ? ReferenceTime.now() : optReferenceTime; List validCerts = parseValidCertificates(certificates, policy, referenceTime.getTimestamp()); LOGGER.debug("Successfully parsed " + validCerts.size() + " certificates."); diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/IntegerUtils.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/IntegerUtils.java deleted file mode 100644 index 378858f3..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/IntegerUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra; - -public final class IntegerUtils { - - private IntegerUtils() { - - } - - /** - * Backported method from Java 8. - * - * @param x x - * @param y y - * @return result of comparison - */ - public static int compare(int x, int y) { - // noinspection UseCompareMethod - return x < y ? -1 : (x == y ? 0 : 1); - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Certification.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Certification.java deleted file mode 100644 index 6cf97b58..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Certification.java +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import java.util.Date; - -public class Certification { - - private final CertSynopsis issuer; - private final CertSynopsis target; - private final Optional userId; - - private final Date creationTime; - private final Optional expirationTime; - private final boolean exportable; - private final int trustAmount; - private final Depth trustDepth; - private final RegexSet regex; - - public Certification( - CertSynopsis issuer, - CertSynopsis target, - Optional userId, - Date creationTime, - Optional expirationTime, - boolean exportable, - int trustAmount, - Depth trustDepth, - RegexSet regex) { - this.issuer = issuer; - this.target = target; - this.userId = userId; - this.creationTime = creationTime; - this.expirationTime = expirationTime; - this.exportable = exportable; - this.trustAmount = trustAmount; - this.trustDepth = trustDepth; - this.regex = regex; - } - - public Certification(CertSynopsis issuer, - Optional targetUserId, - CertSynopsis target, - Date creationTime) { - this.issuer = issuer; - this.target = target; - this.userId = targetUserId; - this.creationTime = creationTime; - - this.expirationTime = Optional.empty(); - this.exportable = true; - this.trustDepth = Depth.limited(0); - this.trustAmount = 120; - this.regex = RegexSet.wildcard(); - } - - /** - * Get the issuer of the certification. - * - * @return issuer - */ - public CertSynopsis getIssuer() { - return issuer; - } - - /** - * Get the target of the certification. - * - * @return target - */ - public CertSynopsis getTarget() { - return target; - } - - /** - * Get the target user-id. - * - * @return user-id - */ - public Optional getUserId() { - return userId; - } - - /** - * Get the creation time of the certification. - * - * @return creation time - */ - public Date getCreationTime() { - return creationTime; - } - - /** - * Get the (optional) expiration time of the certification. - * - * @return optional expiration time - */ - public Optional getExpirationTime() { - return expirationTime; - } - - /** - * Return true if the certification is marked as exportable. - * - * @return exportable - */ - public boolean isExportable() { - return exportable; - } - - /** - * Get the trust amount of the certification. - * - * @return trust amount - */ - public int getTrustAmount() { - return trustAmount; - } - - /** - * Get the trust depth of the certification. - * - * @return trust depth - */ - public Depth getTrustDepth() { - return trustDepth; - } - - /** - * Return the set of regular expressions. - * - * @return regex set - */ - public RegexSet getRegexes() { - return regex; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(issuer.getFingerprint()).append((issuer.getUserIds().isEmpty() ? " " : " (" + issuer.getUserIds().keySet().iterator().next() + ") ")); - sb.append(userId.isPresent() ? "certifies" : "delegates to").append(userId.isPresent() ? " [" + userId.get() + "] " : " ").append(target.getFingerprint()) - .append(userId.isEmpty() && !target.getUserIds().isEmpty() ? " (" + target.getUserIds().keySet().iterator().next() + ")" : ""); - return sb.toString(); - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/CertificationSet.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/CertificationSet.java deleted file mode 100644 index 24a667a7..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/CertificationSet.java +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nonnull; - -/** - * A {@link CertificationSet} is a set of {@link Certification Certifications} made by the same issuer, on the same - * target certificate. - * In some sense, a {@link CertificationSet} can be considered an edge in the web of trust. - */ -public final class CertificationSet { - - private final CertSynopsis issuer; - private final CertSynopsis target; - - private final Map, List> certifications; - - /** - * Create an empty {@link CertificationSet}. - * - * @param issuer issuer - * @param target target - * @return empty set - */ - public static CertificationSet empty(CertSynopsis issuer, CertSynopsis target) { - return new CertificationSet(issuer, target, new HashMap<>()); - } - - public static CertificationSet fromCertification(Certification certification) { - CertificationSet set = CertificationSet.empty(certification.getIssuer(), certification.getTarget()); - set.add(certification); - return set; - } - - private CertificationSet(CertSynopsis issuer, - CertSynopsis target, - Map, List> certifications) { - this.issuer = issuer; - this.target = target; - this.certifications = new HashMap<>(certifications); - } - - public CertSynopsis getIssuer() { - return issuer; - } - - public CertSynopsis getTarget() { - return target; - } - - public Map, List> getCertifications() { - // Copy to avoid side effects - Map, List> copy = new HashMap<>(); - for (Optional key : certifications.keySet()) { - copy.put(key, new ArrayList<>(certifications.get(key))); - } - return copy; - } - - /** - * Merge this {@link CertificationSet} with another instance. - * After the operation, this will contain {@link Certification Certifications} from both sets. - * - * @param other other {@link CertificationSet} - */ - public void merge(@Nonnull CertificationSet other) { - if (other == this) { - return; - } - - if (!issuer.getFingerprint().equals(other.issuer.getFingerprint())) { - throw new IllegalArgumentException("Issuer fingerprint mismatch."); - } - - if (!target.getFingerprint().equals(other.target.getFingerprint())) { - throw new IllegalArgumentException("Target fingerprint mismatch."); - } - - for (Map.Entry, List> entry : other.certifications.entrySet()) { - for (Certification certification : entry.getValue()) { - add(certification); - } - } - } - - /** - * Add a {@link Certification} into this {@link CertificationSet}. - * - * @param certification certification - */ - public void add(@Nonnull Certification certification) { - if (!issuer.getFingerprint().equals(certification.getIssuer().getFingerprint())) { - throw new IllegalArgumentException("Issuer fingerprint mismatch."); - } - if (!target.getFingerprint().equals(certification.getTarget().getFingerprint())) { - throw new IllegalArgumentException("Target fingerprint mismatch."); - } - - List certificationsForUserId = certifications.get(certification.getUserId()); - // noinspection Java8MapApi - if (certificationsForUserId == null) { - certificationsForUserId = new ArrayList<>(); - certifications.put(certification.getUserId(), certificationsForUserId); - } - // TODO: Prevent duplicates, only keep newest timestamped sig? - certificationsForUserId.add(certification); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (Map.Entry, List> entry : certifications.entrySet()) { - for (Certification certification : entry.getValue()) { - sb.append(certification).append('\n'); - } - } - return sb.toString(); - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Depth.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Depth.java deleted file mode 100644 index 18968424..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Depth.java +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import org.pgpainless.wot.dijkstra.IntegerUtils; - -import javax.annotation.Nonnull; - -public final class Depth implements Comparable { - - private final Optional depth; - - private Depth(Optional depth) { - this.depth = depth; - if (!isUnconstrained() && (getLimit().get() < 0 || getLimit().get() > 255)) { - throw new IllegalArgumentException("Depth must be a value between 0 and 255"); - } - } - - public static Depth unconstrained() { - return new Depth(Optional.empty()); - } - - public static Depth limited(int depth) { - return new Depth(Optional.just(depth)); - } - - public static Depth auto(int depth) { - return depth == 255 ? unconstrained() : limited(depth); - } - - public Optional getLimit() { - return depth; - } - - public boolean isUnconstrained() { - return getLimit().isEmpty(); - } - - public Depth decrease(int value) { - if (isUnconstrained()) { - return unconstrained(); - } - if (getLimit().get() >= value) { - return limited(getLimit().get() - value); - } - throw new IllegalArgumentException("Depth cannot be decreased."); - } - - @Override - public int compareTo(@Nonnull Depth o) { - if (isUnconstrained()) { - if (o.isUnconstrained()) { - return 0; - } else { - return 1; - } - } else { - if (o.isUnconstrained()) { - return -1; - } else { - return IntegerUtils.compare(getLimit().get(), o.getLimit().get()); - } - } - } - - @Override - public String toString() { - return isUnconstrained() ? "unconstrained" : getLimit().get().toString(); - } - - public Depth min(Depth other) { - if (compareTo(other) <= 0) { - return this; - } else { - return other; - } - } - -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Network.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Network.java deleted file mode 100644 index 7d650435..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Network.java +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; - -import org.pgpainless.key.OpenPgpFingerprint; - -/** - * A network consists of nodes, and edges between them. - * For the Web of Trust, nodes consist of {@link CertSynopsis CertSynopses}, while the edges between the nodes are - * {@link CertificationSet CertificationSets}. - * Edges can hereby be accessed in two ways: - *
    - *
  • {@link #getEdges()} returns a {@link Map} keyed by the {@link OpenPgpFingerprint fingerprint} of an issuer, - * whose values are {@link List Lists} containing all edges originating from the issuer.
  • - *
  • {@link #getReverseEdges()} on the other hand returns a {@link Map} keyed by the - * {@link OpenPgpFingerprint fingerprint} of a target, whose value are {@link List Lists} containing all edges - * pointing to the target.
  • - *
- */ -public class Network { - - private final Map nodes; - private final Map> edges; - private final Map> reverseEdges; - private final ReferenceTime referenceTime; - - /** - * Create a {@link Network} from a set of nodes, edges, reversed edges and a reference time. - * - * @param nodes map containing all nodes of the network, keyed by their fingerprints - * @param edges map containing all edges of the network, keyed by the fingerprint of the issuer - * @param reverseEdges map containing all reversed edges of the network, keyed by the fingerprint of the target - * @param referenceTime reference time - */ - public Network(Map nodes, - Map> edges, - Map> reverseEdges, - ReferenceTime referenceTime) { - this.nodes = nodes; - this.edges = edges; - this.reverseEdges = reverseEdges; - this.referenceTime = referenceTime; - } - - /** - * Create an empty {@link Network}. - * - * @param referenceTime reference time for evaluation - * @return network - */ - public static Network empty(@Nonnull ReferenceTime referenceTime) { - return new Network( - new HashMap<>(), - new HashMap<>(), - new HashMap<>(), - referenceTime); - } - - /** - * Return all nodes ({@link CertSynopsis}) of the {@link Network}, indexed by their - * {@link OpenPgpFingerprint fingerprints}. - * - * @return nodes of the network - */ - public Map getNodes() { - return new HashMap<>(nodes); - } - - /** - * Return all edges of the {@link Network}, indexed by the {@link OpenPgpFingerprint fingerprint} of the issuer. - * An edge consists of a {@link CertificationSet} containing all signatures made by the issuer on the target. - * TODO: Do we care about immutability, or can we ignore that issue since Network is only used by - * WebOfTrust anyways? - * - * @return map of edges - */ - public Map> getEdges() { - return new HashMap<>(edges); - } - - /** - * Return all reversed edges of the {@link Network}, indexed by the {@link OpenPgpFingerprint fingerprint} of the target. - * TODO: Do we care about immutability, or can we ignore that issue since Network is only used by - * WebOfTrust anyways? - * - * @return map of reversed edges - */ - public Map> getReverseEdges() { - return new HashMap<>(reverseEdges); - } - - /** - * Return the total number of edges on the network. - * - * @return number of edges - */ - public int getNumberOfEdges() { - int num = 0; - for (List outEdges : edges.values()) { - num += outEdges.size(); - } - return num; - } - - /** - * Return the total number of signatures the network comprises. - * - * @return number of signatures - */ - public int getNumberOfSignatures() { - int num = 0; - for (List edgesPerIssuer : edges.values()) { - for (CertificationSet edge : edgesPerIssuer) { - for (List sigsPerDatum : edge.getCertifications().values()) { - num += sigsPerDatum.size(); - } - } - } - return num; - } - - /** - * Return the {@link ReferenceTime} which was used when creating the {@link Network}. - * - * @return reference time - */ - public ReferenceTime getReferenceTime() { - return referenceTime; - } - - public String toString() { - int edgeNum = 0; - for (List edgesFrom : edges.values()) { - edgeNum += edgesFrom.size(); - } - - StringBuilder sb = new StringBuilder("Network with " + getNodes().size() + " nodes, " + edgeNum + " edges:\n"); - for (OpenPgpFingerprint issuer : getNodes().keySet()) { - List edges = getEdges().get(issuer); - if (edges == null) { - continue; - } - for (CertificationSet edge : edges) { - sb.append(edge); - } - } - return sb.toString(); - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Optional.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Optional.java deleted file mode 100644 index 57875fcd..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Optional.java +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class Optional { - - private final T item; - - public static Optional empty() { - return new Optional<>(); - } - - public static Optional just(@Nonnull T item) { - return new Optional<>(item); - } - - public static Optional maybe(@Nullable T item) { - return item == null ? empty() : just(item); - } - - private Optional() { - this.item = null; - } - - private Optional(@Nonnull T item) { - this.item = item; - } - - public boolean isEmpty() { - return item == null; - } - - public boolean isPresent() { - return item != null; - } - - public @Nonnull T get() { - if (item == null) { - throw new NullPointerException("Item is null."); - } - return item; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - - if (!(obj instanceof Optional)) { - return false; - } - - Optional other = (Optional) obj; - if (isEmpty() && other.isEmpty()) { - return true; - } - - if (isPresent() && isPresent()) { - return get().equals(other.get()); - } - - return false; - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Path.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Path.java deleted file mode 100644 index 1f40c9ca..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Path.java +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import java.util.ArrayList; -import java.util.List; - -public class Path { - - private CertSynopsis root; - private List edges; - private Depth residualDepth; - - public Path(CertSynopsis root) { - this.root = root; - this.edges = new ArrayList<>(); - this.residualDepth = Depth.unconstrained(); - } - - public CertSynopsis getRoot() { - return root; - } - - public CertSynopsis getTarget() { - if (edges.isEmpty()) { - return getRoot(); - } else { - return edges.get(edges.size() - 1).getTarget(); - } - } - - public List getCertificates() { - List certs = new ArrayList<>(); - certs.add(getRoot()); - for (Certification edge : edges) { - certs.add(edge.getTarget()); - } - return certs; - } - - public int getLength() { - return edges.size() + 1; - } - - public List getCertifications() { - return new ArrayList<>(edges); - } - - public Depth getResidualDepth() { - return residualDepth; - } - - public int getAmount() { - if (edges.isEmpty()) { - return 120; - } - int min = 255; - for (Certification edge : edges) { - min = Math.min(min, edge.getTrustAmount()); - } - return min; - } - - public void append(Certification certification) { - if (!getTarget().getFingerprint().equals(certification.getIssuer().getFingerprint())) { - throw new IllegalArgumentException("Cannot append certification to path: Path's tail is not issuer of the certification."); - } - - if (!residualDepth.isUnconstrained() && residualDepth.getLimit().get() == 0) { - throw new IllegalArgumentException("Not enough depth."); - } - - boolean cyclic = getRoot().getFingerprint().equals(certification.getTarget().getFingerprint()); - for (int i = 0; i < edges.size() && !cyclic; i++) { - Certification edge = edges.get(i); - - if (edge.getTarget().getFingerprint().equals(certification.getTarget().getFingerprint())) { - if (i == edges.size() - 1) { - cyclic = edge.getUserId().equals(certification.getUserId()); - } else { - cyclic = true; - } - } - } - if (cyclic) { - throw new IllegalArgumentException("Adding the certification to the path would create a cycle."); - } - - residualDepth = certification.getTrustDepth().min(residualDepth.decrease(1)); - edges.add(certification); - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Paths.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Paths.java deleted file mode 100644 index 0e96ef35..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/Paths.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import java.util.ArrayList; -import java.util.List; - -public class Paths { - - private final List paths = new ArrayList<>(); - - public List getPaths() { - return new ArrayList<>(paths); - } - - public void add(Path path, int amount) { - if (amount <= path.getAmount()) { - throw new AssertionError(); - } - this.paths.add(new Item(path, amount)); - } - - public int getAmount() { - int sum = 0; - for (Item item : paths) { - sum += item.amount; - } - return sum; - } - - public static class Item { - private final Path path; - private final int amount; - - public Item(Path path, int amount) { - this.path = path; - this.amount = amount; - } - - public Path getPath() { - return path; - } - - public int getAmount() { - return amount; - } - } -} diff --git a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/RegexSet.java b/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/RegexSet.java deleted file mode 100644 index 59807ff3..00000000 --- a/wot-dijkstra/src/main/java/org/pgpainless/wot/dijkstra/sq/RegexSet.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.wot.dijkstra.sq; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; - -public final class RegexSet { - - private final Set regexStrings; - - private RegexSet(Set regexStrings) { - this.regexStrings = regexStrings; - } - - public static RegexSet fromExpressionList(@Nonnull List regexList) { - Set regexStringSet = new HashSet<>(regexList); - return new RegexSet(regexStringSet); - } - - public static RegexSet fromExpression(@Nonnull String regex) { - return fromExpressionList(Collections.singletonList(regex)); - } - - public static RegexSet wildcard() { - return fromExpressionList(Collections.emptyList()); - } - - public boolean matches(String string) { - if (regexStrings.isEmpty()) { - return true; - } - - for (String regex : regexStrings) { - Matcher matcher = Pattern.compile(regex).matcher(string); - if (matcher.matches()) { - return true; - } - } - - return false; - } -} diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertSynopsis.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertSynopsis.kt index 9af72037..58767789 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertSynopsis.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertSynopsis.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.wot.dijkstra.sq import org.pgpainless.algorithm.RevocationState diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Certification.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Certification.kt new file mode 100644 index 00000000..dc465d12 --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Certification.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +import java.util.* + +data class Certification( + val issuer: CertSynopsis, + val target: CertSynopsis, + val userId: String?, + val creationTime: Date, + val expirationTime: Date?, + val exportable: Boolean, + val trustAmount: Int, + val trustDepth: Depth, + val regex: RegexSet +) { + + constructor( + issuer: CertSynopsis, + targetUserId: String?, + target: CertSynopsis, + creationTime: Date) : + this(issuer, target, targetUserId, creationTime, null, true, 120, Depth.limited(0), RegexSet.wildcard()) + + override fun toString(): String { + val relation = if (userId != null) { + "certifies" + } else { + "delegates to" + } + val relationTarget = if (userId != null) { + "[$userId] ${target.fingerprint}" + } else { + "$target" + } + return "$issuer $relation $relationTarget" + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertificationSet.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertificationSet.kt new file mode 100644 index 00000000..0562db53 --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/CertificationSet.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +data class CertificationSet( + val issuer: CertSynopsis, + val target: CertSynopsis, + val certifications: MutableMap>) { + + companion object { + + @JvmStatic + fun empty(issuer: CertSynopsis, target: CertSynopsis): CertificationSet { + return CertificationSet(issuer, target, HashMap()) + } + + @JvmStatic + fun fromCertification(certification: Certification) : CertificationSet { + val set = empty(certification.issuer, certification.target) + set.add(certification) + return set + } + } + + fun merge(other: CertificationSet) { + if (other == this) { + return + } + + require(issuer.fingerprint == other.issuer.fingerprint) { "Issuer fingerprint mismatch." } + require(target.fingerprint == other.target.fingerprint) { "Target fingerprint mismatch." } + + for (userId in other.certifications.keys) { + for (certification in other.certifications[userId]!!) { + add(certification) + } + } + } + + fun add(certification : Certification) { + require(issuer.fingerprint == certification.issuer.fingerprint) { "Issuer fingerprint mismatch." } + require(target.fingerprint == certification.target.fingerprint) { "Target fingerprint mismatch." } + + var certificationsForUserId: MutableList? = certifications[certification.userId] + if (certificationsForUserId == null) { + certificationsForUserId = ArrayList() + certifications[certification.userId] = certificationsForUserId + } + certificationsForUserId.add(certification) + } + + override fun toString(): String { + return "$certifications" + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Depth.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Depth.kt new file mode 100644 index 00000000..c930e291 --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Depth.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +class Depth(val limit: Int?) : Comparable { + + companion object { + @JvmStatic + fun unconstrained() : Depth { + return Depth(null) + } + + @JvmStatic + fun limited(limit: Int): Depth { + require(limit in 0..255) { + "Trust depth MUST be a value between 0 and 255." + } + return Depth(limit) + } + + @JvmStatic + fun auto(limit: Int): Depth { + return if (limit == 255) { + unconstrained() + } else { + limited(limit) + } + } + } + + fun isUnconstrained() : Boolean { + return limit == null + } + + fun decrease(value : Int) : Depth { + return if (isUnconstrained()) { + unconstrained() + } else { + if (limit!! >= value) { + limited(limit - value) + } else { + throw IllegalArgumentException("Depth cannot be decreased.") + } + } + } + + fun min(other: Depth) : Depth { + return if (compareTo(other) <= 0) { + this + } else { + other + } + } + + override fun compareTo(o: Depth): Int { + return if (isUnconstrained()) { + if (o.isUnconstrained()) { + 0 + } else { + 1 + } + } else { + if (o.isUnconstrained()) { + -1 + } else { + limit!!.compareTo(o.limit!!) + } + } + } + + override fun toString() : String { + return if (isUnconstrained()) { "unconstrained" } else { limit!!.toString() } + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Network.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Network.kt new file mode 100644 index 00000000..23eb088f --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Network.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +import org.pgpainless.key.OpenPgpFingerprint + +/** + * A network consists of nodes, and edges between them. + * For the Web of Trust, nodes consist of [CertSynopses][CertSynopsis], while the edges between the nodes are + * [CertificationSets][CertificationSet]. + * + * @constructor creates a new network + * @param nodes contains a [Map] of [CertSynopsis] keyed by their [OpenPgpFingerprint] + * @param edges [Map] keyed by the [fingerprint][OpenPgpFingerprint] of an issuer, whose values are [Lists][List] containing all edges originating from the issuer. + * @param reverseEdges [Map] keyed by the [fingerprint][OpenPgpFingerprint] of a target, whose values are [Lists][List] containing all edges pointing to the target + * @param referenceTime reference time at which the [Network] was built + */ +class Network( + val nodes: Map, + val edges: Map>, + val reverseEdges: Map>, + val referenceTime: ReferenceTime) { + + companion object { + @JvmStatic + fun empty(referenceTime: ReferenceTime): Network { + return Network(HashMap(), HashMap(), HashMap(), referenceTime) + } + } + + val numberOfEdges: Int + get() { + return edges.values.sumOf { it.size } + } + + val numberOfSignatures: Int + get() { + return edges.values + .flatten() + .flatMap { it.certifications.values } + .sumOf { it.size } + } + + override fun toString(): String { + val sb = StringBuilder() + sb.append("Network with ${nodes.size} nodes, $numberOfEdges edges:\n") + for (issuer in nodes.keys) { + val outEdges = edges[issuer] ?: continue + for (edge in outEdges) { + sb.append(edge) + } + } + return sb.toString() + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Path.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Path.kt new file mode 100644 index 00000000..1ed7cd38 --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Path.kt @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +import kotlin.math.min + +class Path( + val root: CertSynopsis, + val edges: MutableList, + var residualDepth: Depth +) { + constructor(root: CertSynopsis) : this( + root, mutableListOf(), Depth.unconstrained()) + + val target: CertSynopsis + get() { + return if (edges.isEmpty()) { + root + } else { + edges[edges.size - 1].target + } + } + + val certificates: List + get() { + val certs: MutableList = ArrayList() + certs.add(root) + for (certification in edges) { + certs.add(certification.target) + } + return certs + } + + val length: Int + get() = edges.size + 1 + + val certifications: List + get() = ArrayList(edges) + + val amount: Int + get() = if (edges.isEmpty()) { + 120 + } else { + var min = 255 + for (edge in edges) { + min = min(min, edge.trustAmount) + } + min + } + + fun append(certification: Certification) { + require(target.fingerprint == certification.issuer.fingerprint) { + "Cannot append certification to path: Path's tail is not issuer of the certification." + } + require(residualDepth.isUnconstrained() || residualDepth.limit!! > 0) { + "Not enough depth." + } + + var cyclic = root.fingerprint == certification.target.fingerprint + for (i in 0..edges.size) { + val edge = edges[i] + if (cyclic) { + break + } + if (edge.target.fingerprint == certification.target.fingerprint) { + cyclic = if (i == edges.size - 1) { + edge.userId == certification.userId + } else { + true + } + } + } + require(!cyclic) { "Adding the certification to the path would create a cycle." } + + residualDepth = certification.trustDepth.min(residualDepth.decrease(1)) + edges.add(certification) + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Paths.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Paths.kt new file mode 100644 index 00000000..35e9fc41 --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/Paths.kt @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +class Paths(val paths: MutableList) { + + fun add(path: Path, amount: Int) { + require(amount <= path.amount) { + "Amount too small. TODO: Better error message" + } + paths.add(Item(path, amount)) + } + + val amount: Int + get() { + return paths.sumOf { it.amount } + } + + data class Item(val path: Path, val amount: Int) { + + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/RegexSet.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/RegexSet.kt new file mode 100644 index 00000000..c2230f0d --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/dijkstra/sq/RegexSet.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.dijkstra.sq + +import java.util.regex.Pattern + +data class RegexSet(val regexStrings: Set) { + + companion object { + @JvmStatic + fun fromExpressionList(regexList: List): RegexSet { + return RegexSet(regexList.toSet()) + } + + @JvmStatic + fun fromExpression(regex: String): RegexSet { + return fromExpressionList(listOf(regex)) + } + + @JvmStatic + fun wildcard(): RegexSet { + return fromExpressionList(listOf()) + } + } + + fun matches(string: String): Boolean { + if (regexStrings.isEmpty()) { + return true + } + + for (regex in regexStrings) { + val matcher = Pattern.compile(regex).matcher(string) + if (matcher.matches()) { + return true + } + } + return false + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/test/java/org/pgpainless/wot/dijkstra/sq/DepthTest.java b/wot-dijkstra/src/test/java/org/pgpainless/wot/dijkstra/sq/DepthTest.java index 64ffa395..88dd0d50 100644 --- a/wot-dijkstra/src/test/java/org/pgpainless/wot/dijkstra/sq/DepthTest.java +++ b/wot-dijkstra/src/test/java/org/pgpainless/wot/dijkstra/sq/DepthTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -17,15 +19,15 @@ public class DepthTest { public void testUnlimitedItem() { Depth depth = Depth.unconstrained(); assertTrue(depth.isUnconstrained()); - assertFalse(depth.getLimit().isPresent()); + assertNull(depth.getLimit()); } @Test public void testLimitedItem() { Depth limited = Depth.limited(2); assertFalse(limited.isUnconstrained()); - assertTrue(limited.getLimit().isPresent()); - assertEquals(2, limited.getLimit().get()); + assertNotNull(limited.getLimit()); + assertEquals(2, limited.getLimit()); } @Test @@ -40,7 +42,7 @@ public class DepthTest { Depth limited = Depth.limited(1); Depth decreased = limited.decrease(1); assertFalse(decreased.isUnconstrained()); - assertEquals(0, decreased.getLimit().get()); + assertEquals(0, decreased.getLimit()); } @Test @@ -76,7 +78,7 @@ public class DepthTest { public void testAutoLimited() { Depth depth = Depth.auto(120); assertFalse(depth.isUnconstrained()); - assertEquals(120, depth.getLimit().get()); + assertEquals(120, depth.getLimit()); } @Test