diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java new file mode 100644 index 00000000..ab45a896 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check; + +import org.pgpainless.exception.MalformedOpenPgpMessageException; + +public class OpenPgpMessageSyntax implements Syntax { + + @Override + public Transition transition(State from, InputAlphabet input, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException { + switch (from) { + case OpenPgpMessage: + return fromOpenPgpMessage(input, stackItem); + case LiteralMessage: + return fromLiteralMessage(input, stackItem); + case CompressedMessage: + return fromCompressedMessage(input, stackItem); + case EncryptedMessage: + return fromEncryptedMessage(input, stackItem); + case Valid: + return fromValid(input, stackItem); + } + + throw new MalformedOpenPgpMessageException(from, input, stackItem); + } + + Transition fromOpenPgpMessage(InputAlphabet input, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException { + if (stackItem != StackAlphabet.msg) { + throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem); + } + + switch (input) { + case LiteralData: + return new Transition(State.LiteralMessage); + + case Signature: + return new Transition(State.OpenPgpMessage, StackAlphabet.msg); + + case OnePassSignature: + return new Transition(State.OpenPgpMessage, StackAlphabet.ops, StackAlphabet.msg); + + case CompressedData: + return new Transition(State.CompressedMessage); + + case EncryptedData: + return new Transition(State.EncryptedMessage); + + case EndOfSequence: + default: + throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem); + } + } + + Transition fromLiteralMessage(InputAlphabet input, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException { + switch (input) { + case Signature: + if (stackItem == StackAlphabet.ops) { + return new Transition(State.LiteralMessage); + } + break; + + case EndOfSequence: + if (stackItem == StackAlphabet.terminus) { + return new Transition(State.Valid); + } + break; + } + + throw new MalformedOpenPgpMessageException(State.LiteralMessage, input, stackItem); + } + + Transition fromCompressedMessage(InputAlphabet input, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException { + switch (input) { + case Signature: + if (stackItem == StackAlphabet.ops) { + return new Transition(State.CompressedMessage); + } + break; + + case EndOfSequence: + if (stackItem == StackAlphabet.terminus) { + return new Transition(State.Valid); + } + break; + } + + throw new MalformedOpenPgpMessageException(State.CompressedMessage, input, stackItem); + } + + Transition fromEncryptedMessage(InputAlphabet input, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException { + switch (input) { + case Signature: + if (stackItem == StackAlphabet.ops) { + return new Transition(State.EncryptedMessage); + } + break; + + case EndOfSequence: + if (stackItem == StackAlphabet.terminus) { + return new Transition(State.Valid); + } + break; + } + + throw new MalformedOpenPgpMessageException(State.EncryptedMessage, input, stackItem); + } + + Transition fromValid(InputAlphabet input, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException { + throw new MalformedOpenPgpMessageException(State.Valid, input, stackItem); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java index 550cb855..94c1739e 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java @@ -14,177 +14,31 @@ import java.util.List; import java.util.Stack; import static org.pgpainless.decryption_verification.syntax_check.StackAlphabet.msg; -import static org.pgpainless.decryption_verification.syntax_check.StackAlphabet.ops; import static org.pgpainless.decryption_verification.syntax_check.StackAlphabet.terminus; public class PDA { private static final Logger LOGGER = LoggerFactory.getLogger(PDA.class); - /** - * Set of states of the automaton. - * Each state defines its valid transitions in their {@link State#transition(InputAlphabet, PDA)} method. - */ - public enum State { - - OpenPgpMessage { - @Override - State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - if (stackItem != msg) { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - switch (input) { - - case LiteralData: - return LiteralMessage; - - case Signature: - automaton.pushStack(msg); - return OpenPgpMessage; - - case OnePassSignature: - automaton.pushStack(ops); - automaton.pushStack(msg); - return OpenPgpMessage; - - case CompressedData: - return CompressedMessage; - - case EncryptedData: - return EncryptedMessage; - - case EndOfSequence: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - LiteralMessage { - @Override - State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - switch (input) { - - case Signature: - if (stackItem == ops) { - return LiteralMessage; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case EndOfSequence: - if (stackItem == terminus && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case LiteralData: - case OnePassSignature: - case CompressedData: - case EncryptedData: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - CompressedMessage { - @Override - State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - switch (input) { - case Signature: - if (stackItem == ops) { - return CompressedMessage; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case EndOfSequence: - if (stackItem == terminus && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case LiteralData: - case OnePassSignature: - case CompressedData: - case EncryptedData: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - EncryptedMessage { - @Override - State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - switch (input) { - case Signature: - if (stackItem == ops) { - return EncryptedMessage; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case EndOfSequence: - if (stackItem == terminus && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case LiteralData: - case OnePassSignature: - case CompressedData: - case EncryptedData: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - Valid { - @Override - State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException { - throw new MalformedOpenPgpMessageException(this, input, null); - } - }, - ; - - /** - * Pop the automatons stack and transition to another state. - * If no valid transition from the current state is available given the popped stack item and input symbol, - * a {@link MalformedOpenPgpMessageException} is thrown. - * Otherwise, the stack is manipulated according to the valid transition and the new state is returned. - * - * @param input input symbol - * @param automaton automaton - * @return new state of the automaton - * @throws MalformedOpenPgpMessageException in case of an illegal input symbol - */ - abstract State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException; - } - private final Stack stack = new Stack<>(); private final List inputs = new ArrayList<>(); // keep track of inputs for debugging / error reporting private State state; + private Syntax syntax = new OpenPgpMessageSyntax(); public PDA() { state = State.OpenPgpMessage; - stack.push(terminus); - stack.push(msg); + pushStack(terminus); + pushStack(msg); } public void next(InputAlphabet input) throws MalformedOpenPgpMessageException { try { - state = state.transition(input, this); + Transition transition = syntax.transition(state, input, popStack()); inputs.add(input); + state = transition.getNewState(); + for (StackAlphabet item : transition.getPushedItems()) { + pushStack(item); + } } catch (MalformedOpenPgpMessageException e) { MalformedOpenPgpMessageException wrapped = new MalformedOpenPgpMessageException("Malformed message: After reading stream " + Arrays.toString(inputs.toArray()) + ", token '" + input + "' is unexpected and illegal.", e); @@ -230,6 +84,9 @@ public class PDA { * @return stack item */ private StackAlphabet popStack() { + if (stack.isEmpty()) { + return null; + } return stack.pop(); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/State.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/State.java new file mode 100644 index 00000000..9dee9af1 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/State.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check; + +/** + * Set of states of the automaton. + */ +public enum State { + OpenPgpMessage, + LiteralMessage, + CompressedMessage, + EncryptedMessage, + Valid +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java new file mode 100644 index 00000000..47813a9e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check; + +import org.pgpainless.exception.MalformedOpenPgpMessageException; + +public interface Syntax { + + Transition transition(State from, InputAlphabet inputAlphabet, StackAlphabet stackItem) + throws MalformedOpenPgpMessageException; +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java new file mode 100644 index 00000000..bbc28e58 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Transition { + + private final List pushedItems = new ArrayList<>(); + private final State newState; + + public Transition(State newState, StackAlphabet... pushedItems) { + this.newState = newState; + this.pushedItems.addAll(Arrays.asList(pushedItems)); + } + + public State getNewState() { + return newState; + } + + public List getPushedItems() { + return new ArrayList<>(pushedItems); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java b/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java index aa158746..db8d0df6 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java +++ b/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java @@ -5,8 +5,8 @@ package org.pgpainless.exception; import org.pgpainless.decryption_verification.syntax_check.InputAlphabet; -import org.pgpainless.decryption_verification.syntax_check.PDA; import org.pgpainless.decryption_verification.syntax_check.StackAlphabet; +import org.pgpainless.decryption_verification.syntax_check.State; /** * Exception that gets thrown if the OpenPGP message is malformed. @@ -20,7 +20,7 @@ public class MalformedOpenPgpMessageException extends RuntimeException { super(message); } - public MalformedOpenPgpMessageException(PDA.State state, InputAlphabet input, StackAlphabet stackItem) { + public MalformedOpenPgpMessageException(State state, InputAlphabet input, StackAlphabet stackItem) { this("There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack."); }