diff --git a/src/main/java/de/rub/nds/modifiablevariable/ModifiableVariableProperty.java b/src/main/java/de/rub/nds/modifiablevariable/ModifiableVariableProperty.java
index 0cdcad4c..320a20d2 100644
--- a/src/main/java/de/rub/nds/modifiablevariable/ModifiableVariableProperty.java
+++ b/src/main/java/de/rub/nds/modifiablevariable/ModifiableVariableProperty.java
@@ -16,81 +16,146 @@
* Annotation interface for marking and categorizing modifiable variables within a class.
*
*
This annotation provides metadata about a modifiable variable field, including its semantic
- * type (what kind of data it represents) and format (how the data is encoded). This information can
- * be used for reflection-based analysis, serialization, or other operations that need to understand
- * the purpose of different variables.
- *
- *
//TODO This class has not been touched or used much for a while and needs refactoring.
+ * type (what kind of data it represents) and format (how the data is encoded). This information is
+ * used for reflection-based analysis, serialization, and testing scenarios.
*
*
The annotation is retained at runtime and can only be applied to fields.
+ *
+ *
Usage examples:
+ *
+ *
{@code
+ * // Variable length field
+ * @ModifiableVariableProperty(purpose = Purpose.LENGTH, minLength = 1, maxLength = 4)
+ * private ModifiableInteger messageLength;
+ *
+ * // Signature with specific encoding and length
+ * @ModifiableVariableProperty(
+ * purpose = Purpose.SIGNATURE,
+ * encoding = Encoding.ASN1_DER,
+ * minLength = 70,
+ * maxLength = 73)
+ * private ModifiableByteArray digitalSignature;
+ *
+ * // Random value
+ * @ModifiableVariableProperty(purpose = Purpose.RANDOM)
+ * private ModifiableByteArray nonce;
+ *
+ * // Variable-length data with description
+ * @ModifiableVariableProperty(
+ * purpose = Purpose.PLAINTEXT,
+ * encoding = Encoding.UTF8,
+ * maxLength = 1024)
+ * private ModifiableByteArray applicationData;
+ * }
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModifiableVariableProperty {
/**
- * Semantic types that can be assigned to modifiable variables. These values describe the
- * purpose or role of the variable.
+ * Semantic purpose categories for modifiable variables. These describe the role or function of
+ * the variable within any protocol or data structure.
*/
- enum Type {
+ enum Purpose {
/** Variable representing a length field */
LENGTH,
- /** Variable representing a count field */
+ /** Variable representing a count or quantity field */
COUNT,
- /** Variable representing padding */
+ /** Variable representing padding or filler bytes */
PADDING,
- /** Variable representing one or more (array of) TLS constants */
- TLS_CONSTANT,
+ /** Variable representing a protocol constant or enumerated value */
+ CONSTANT,
/** Variable representing a cryptographic signature */
SIGNATURE,
- /** Variable representing encrypted data */
+ /** Variable representing encrypted or ciphered data */
CIPHERTEXT,
- /** Variable representing a message authentication code */
- HMAC,
- /** Variable representing a public key */
- PUBLIC_KEY,
- /** Variable representing a private key */
- PRIVATE_KEY,
- /** Variable representing key material */
+ /** Variable representing a message authentication code or hash */
+ MAC,
+ /** Variable representing cryptographic key material */
KEY_MATERIAL,
/** Variable representing a certificate */
CERTIFICATE,
- /** Variable representing a plain protocol message, always in a decrypted state */
- PLAIN_PROTOCOL_MESSAGE,
- /** Variable representing a plain record */
- PLAIN_RECORD,
- /** Variable representing a cookie */
- COOKIE,
- /** Default type when no specific type is applicable */
- NONE,
- /** Variable that switches behavior */
- BEHAVIOR_SWITCH
+ /** Variable representing plaintext protocol data */
+ PLAINTEXT,
+ /** Variable representing a random value, nonce, or salt */
+ RANDOM,
+ /** Variable representing a session or connection identifier */
+ IDENTIFIER,
+ /** Variable representing a timestamp or temporal value */
+ TIMESTAMP,
+ /** Default purpose when no specific category applies */
+ UNSPECIFIED
}
/**
- * Encoding formats that can be used for modifiable variables. These values describe how the
- * data is encoded.
+ * Encoding formats for modifiable variables. These describe how the data is encoded,
+ * structured, or represented.
*/
- enum Format {
- /** ASN.1 encoding format */
- ASN1,
- /** PKCS#1 encoding format */
+ enum Encoding {
+ /** ASN.1 Distinguished Encoding Rules format */
+ ASN1_DER,
+ /** ASN.1 Basic Encoding Rules format */
+ ASN1_BER,
+ /** PKCS#1 format for RSA key encoding */
PKCS1,
- /** Default format when no specific format is applicable */
- NONE
+ /** PKCS#8 format for private key information */
+ PKCS8,
+ /** X.509 format for certificates */
+ X509,
+ /** PEM (Privacy-Enhanced Mail) text format */
+ PEM,
+ /** Base64 text encoding */
+ BASE64,
+ /** Hexadecimal string representation */
+ HEX_STRING,
+ /** Raw binary data */
+ BINARY,
+ /** UTF-8 text encoding */
+ UTF8,
+ /** Unsigned big-endian encoding */
+ UNSIGNED_BIG_ENDIAN,
+ /** Unsigned little-endian encoding */
+ UNSIGNED_LITTLE_ENDIAN,
+ /** Signed big-endian encoding */
+ SIGNED_BIG_ENDIAN,
+ /** Signed little-endian encoding */
+ SIGNED_LITTLE_ENDIAN,
+
+ /** JSON text format */
+ JSON,
+ /** XML text format */
+ XML,
+ /** Default encoding when not specified */
+ UNSPECIFIED
}
/**
- * Specifies the semantic type of the annotated variable.
+ * Specifies the semantic purpose of the annotated variable.
*
- * @return The type of the variable
+ * @return The purpose category of the variable
*/
- Type type() default Type.NONE;
+ Purpose purpose() default Purpose.UNSPECIFIED;
/**
* Specifies the encoding format of the annotated variable.
*
- * @return The format of the variable
+ * @return The encoding format of the variable
+ */
+ Encoding encoding() default Encoding.UNSPECIFIED;
+
+ /**
+ * Specifies the minimum length (in bytes) of the variable's value. Use -1 to indicate no
+ * minimum constraint.
+ *
+ * @return The minimum length in bytes, or -1 if unconstrained
+ */
+ int minLength() default -1;
+
+ /**
+ * Specifies the maximum length (in bytes) of the variable's value. Use -1 to indicate no
+ * maximum constraint.
+ *
+ * @return The maximum length in bytes, or -1 if unconstrained
*/
- Format format() default Format.NONE;
+ int maxLength() default -1;
}
diff --git a/src/main/java/de/rub/nds/modifiablevariable/util/ModifiableVariableAnalyzer.java b/src/main/java/de/rub/nds/modifiablevariable/util/ModifiableVariableAnalyzer.java
index 326c129f..03d4f9ff 100644
--- a/src/main/java/de/rub/nds/modifiablevariable/util/ModifiableVariableAnalyzer.java
+++ b/src/main/java/de/rub/nds/modifiablevariable/util/ModifiableVariableAnalyzer.java
@@ -9,9 +9,12 @@
import de.rub.nds.modifiablevariable.HoldsModifiableVariable;
import de.rub.nds.modifiablevariable.ModifiableVariable;
+import de.rub.nds.modifiablevariable.ModifiableVariableProperty;
+import de.rub.nds.modifiablevariable.ModifiableVariableProperty.Encoding;
+import de.rub.nds.modifiablevariable.ModifiableVariableProperty.Purpose;
import java.lang.reflect.Field;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -23,6 +26,10 @@
* locate fields that are either of type ModifiableVariable or are annotated with {@link
* HoldsModifiableVariable}.
*
+ * Additionally, this class provides specialized methods for working with the {@link
+ * ModifiableVariableProperty} annotation to categorize and analyze fields by their semantic
+ * meaning, encoding format, and protocol context.
+ *
*
This class cannot be instantiated and all methods are static.
*/
public final class ModifiableVariableAnalyzer {
@@ -205,4 +212,125 @@ public static List getAllModifiableVariableHolders
}
return result;
}
+
+ /**
+ * Retrieves all fields in a class hierarchy that are annotated with ModifiableVariableProperty.
+ *
+ * @param clazz The class to analyze
+ * @return A list of fields annotated with ModifiableVariableProperty
+ */
+ public static List getAnnotatedFields(Class> clazz) {
+ List annotatedFields = new ArrayList<>();
+ Class> currentClass = clazz;
+
+ while (currentClass != null && currentClass != Object.class) {
+ for (Field field : currentClass.getDeclaredFields()) {
+ if (field.isAnnotationPresent(ModifiableVariableProperty.class)) {
+ annotatedFields.add(field);
+ }
+ }
+ currentClass = currentClass.getSuperclass();
+ }
+
+ return annotatedFields;
+ }
+
+ /**
+ * Groups annotated fields by their semantic purpose.
+ *
+ * @param clazz The class to analyze
+ * @return A map where keys are Purpose enum values and values are lists of fields
+ */
+ public static Map> groupFieldsByPurpose(Class> clazz) {
+ return getAnnotatedFields(clazz).stream()
+ .collect(
+ Collectors.groupingBy(
+ field ->
+ field.getAnnotation(ModifiableVariableProperty.class)
+ .purpose()));
+ }
+
+ /**
+ * Groups annotated fields by their encoding format.
+ *
+ * @param clazz The class to analyze
+ * @return A map where keys are Encoding enum values and values are lists of fields
+ */
+ public static Map> groupFieldsByEncoding(Class> clazz) {
+ return getAnnotatedFields(clazz).stream()
+ .collect(
+ Collectors.groupingBy(
+ field ->
+ field.getAnnotation(ModifiableVariableProperty.class)
+ .encoding()));
+ }
+
+ /**
+ * Finds all fields of a specific semantic purpose.
+ *
+ * @param clazz The class to analyze
+ * @param purpose The semantic purpose to search for
+ * @return A list of fields with the specified purpose
+ */
+ public static List getFieldsByPurpose(Class> clazz, Purpose purpose) {
+ return getAnnotatedFields(clazz).stream()
+ .filter(
+ field ->
+ field.getAnnotation(ModifiableVariableProperty.class).purpose()
+ == purpose)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Finds all fields with a specific encoding format.
+ *
+ * @param clazz The class to analyze
+ * @param encoding The encoding format to search for
+ * @return A list of fields with the specified encoding
+ */
+ public static List getFieldsByEncoding(Class> clazz, Encoding encoding) {
+ return getAnnotatedFields(clazz).stream()
+ .filter(
+ field ->
+ field.getAnnotation(ModifiableVariableProperty.class).encoding()
+ == encoding)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Checks if a field has a ModifiableVariableProperty annotation.
+ *
+ * @param field The field to check
+ * @return true if the field is annotated, false otherwise
+ */
+ public static boolean isAnnotated(Field field) {
+ return field.isAnnotationPresent(ModifiableVariableProperty.class);
+ }
+
+ /**
+ * Gets the ModifiableVariableProperty annotation from a field.
+ *
+ * @param field The field to examine
+ * @return The annotation or null if not present
+ */
+ public static ModifiableVariableProperty getAnnotation(Field field) {
+ return field.getAnnotation(ModifiableVariableProperty.class);
+ }
+
+ /**
+ * Validates that all ModifiableVariable fields in a class have proper annotations. This is
+ * useful for ensuring coding standards compliance.
+ *
+ * @param clazz The class to validate
+ * @return A list of field names that are ModifiableVariable but lack annotations
+ */
+ public static List getUnannotatedModifiableVariables(Class> clazz) {
+ List allModifiableFields =
+ ReflectionHelper.getFieldsUpTo(clazz, null, ModifiableVariable.class);
+
+ return allModifiableFields.stream()
+ .filter(field -> !isAnnotated(field))
+ .map(Field::getName)
+ .collect(Collectors.toList());
+ }
}
diff --git a/src/test/java/de/rub/nds/modifiablevariable/ModifiableVariablePropertyTest.java b/src/test/java/de/rub/nds/modifiablevariable/ModifiableVariablePropertyTest.java
index 058d28e4..cedc2a3a 100644
--- a/src/test/java/de/rub/nds/modifiablevariable/ModifiableVariablePropertyTest.java
+++ b/src/test/java/de/rub/nds/modifiablevariable/ModifiableVariablePropertyTest.java
@@ -7,29 +7,50 @@
*/
package de.rub.nds.modifiablevariable;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.*;
-import de.rub.nds.modifiablevariable.ModifiableVariableProperty.Format;
-import de.rub.nds.modifiablevariable.ModifiableVariableProperty.Type;
+import de.rub.nds.modifiablevariable.ModifiableVariableProperty.Encoding;
+import de.rub.nds.modifiablevariable.ModifiableVariableProperty.Purpose;
import de.rub.nds.modifiablevariable.integer.ModifiableInteger;
+import de.rub.nds.modifiablevariable.util.ModifiableVariableAnalyzer;
import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.Test;
public class ModifiableVariablePropertyTest {
// Test class with annotated fields
private static class TestClass {
- @ModifiableVariableProperty(type = Type.LENGTH)
+ @ModifiableVariableProperty(purpose = Purpose.LENGTH, minLength = 1, maxLength = 4)
private ModifiableInteger length;
- @ModifiableVariableProperty(type = Type.COUNT)
+ @ModifiableVariableProperty(purpose = Purpose.COUNT)
private ModifiableInteger count;
- @ModifiableVariableProperty(type = Type.SIGNATURE, format = Format.ASN1)
- private ModifiableInteger signatureWithFormat;
+ @ModifiableVariableProperty(purpose = Purpose.SIGNATURE, encoding = Encoding.ASN1_DER)
+ private ModifiableInteger signatureWithEncoding;
@ModifiableVariableProperty private ModifiableInteger defaultProperty;
+
+ @ModifiableVariableProperty(
+ purpose = Purpose.KEY_MATERIAL,
+ encoding = Encoding.X509,
+ minLength = 32,
+ maxLength = 65)
+ private ModifiableInteger enhancedProperty;
+
+ @ModifiableVariableProperty(purpose = Purpose.CONSTANT)
+ private ModifiableInteger protocolVersion;
+
+ @ModifiableVariableProperty(purpose = Purpose.RANDOM)
+ private ModifiableInteger randomValue;
+
+ @ModifiableVariableProperty(purpose = Purpose.PADDING, minLength = 0, maxLength = 255)
+ private ModifiableInteger paddingLength;
+
+ // Unannotated ModifiableVariable field for testing
+ private ModifiableInteger unannotatedField;
}
@Test
@@ -39,8 +60,10 @@ public void testLengthProperty() throws NoSuchFieldException {
lengthField.getAnnotation(ModifiableVariableProperty.class);
assertNotNull(annotation);
- assertEquals(Type.LENGTH, annotation.type());
- assertEquals(Format.NONE, annotation.format());
+ assertEquals(Purpose.LENGTH, annotation.purpose());
+ assertEquals(Encoding.UNSPECIFIED, annotation.encoding());
+ assertEquals(1, annotation.minLength());
+ assertEquals(4, annotation.maxLength());
}
@Test
@@ -50,19 +73,21 @@ public void testCountProperty() throws NoSuchFieldException {
countField.getAnnotation(ModifiableVariableProperty.class);
assertNotNull(annotation);
- assertEquals(Type.COUNT, annotation.type());
- assertEquals(Format.NONE, annotation.format());
+ assertEquals(Purpose.COUNT, annotation.purpose());
+ assertEquals(Encoding.UNSPECIFIED, annotation.encoding());
+ assertEquals(-1, annotation.minLength());
+ assertEquals(-1, annotation.maxLength());
}
@Test
- public void testSignatureWithFormatProperty() throws NoSuchFieldException {
- Field signatureField = TestClass.class.getDeclaredField("signatureWithFormat");
+ public void testSignatureWithEncodingProperty() throws NoSuchFieldException {
+ Field signatureField = TestClass.class.getDeclaredField("signatureWithEncoding");
ModifiableVariableProperty annotation =
signatureField.getAnnotation(ModifiableVariableProperty.class);
assertNotNull(annotation);
- assertEquals(Type.SIGNATURE, annotation.type());
- assertEquals(Format.ASN1, annotation.format());
+ assertEquals(Purpose.SIGNATURE, annotation.purpose());
+ assertEquals(Encoding.ASN1_DER, annotation.encoding());
}
@Test
@@ -72,7 +97,78 @@ public void testDefaultProperty() throws NoSuchFieldException {
defaultField.getAnnotation(ModifiableVariableProperty.class);
assertNotNull(annotation);
- assertEquals(Type.NONE, annotation.type());
- assertEquals(Format.NONE, annotation.format());
+ assertEquals(Purpose.UNSPECIFIED, annotation.purpose());
+ assertEquals(Encoding.UNSPECIFIED, annotation.encoding());
+ assertEquals(-1, annotation.minLength());
+ assertEquals(-1, annotation.maxLength());
+ }
+
+ @Test
+ public void testEnhancedProperty() throws NoSuchFieldException {
+ Field enhancedField = TestClass.class.getDeclaredField("enhancedProperty");
+ ModifiableVariableProperty annotation =
+ enhancedField.getAnnotation(ModifiableVariableProperty.class);
+
+ assertNotNull(annotation);
+ assertEquals(Purpose.KEY_MATERIAL, annotation.purpose());
+ assertEquals(Encoding.X509, annotation.encoding());
+ assertEquals(32, annotation.minLength());
+ assertEquals(65, annotation.maxLength());
+ }
+
+ @Test
+ public void testNewPurposeEnums() throws NoSuchFieldException {
+ Field versionField = TestClass.class.getDeclaredField("protocolVersion");
+ ModifiableVariableProperty versionAnnotation =
+ versionField.getAnnotation(ModifiableVariableProperty.class);
+
+ assertNotNull(versionAnnotation);
+ assertEquals(Purpose.CONSTANT, versionAnnotation.purpose());
+
+ Field randomField = TestClass.class.getDeclaredField("randomValue");
+ ModifiableVariableProperty randomAnnotation =
+ randomField.getAnnotation(ModifiableVariableProperty.class);
+
+ assertNotNull(randomAnnotation);
+ assertEquals(Purpose.RANDOM, randomAnnotation.purpose());
+
+ Field paddingField = TestClass.class.getDeclaredField("paddingLength");
+ ModifiableVariableProperty paddingAnnotation =
+ paddingField.getAnnotation(ModifiableVariableProperty.class);
+
+ assertNotNull(paddingAnnotation);
+ assertEquals(Purpose.PADDING, paddingAnnotation.purpose());
+ assertEquals(0, paddingAnnotation.minLength());
+ assertEquals(255, paddingAnnotation.maxLength());
+ }
+
+ @Test
+ public void testModifiableVariableAnalyzer() {
+ List annotatedFields =
+ ModifiableVariableAnalyzer.getAnnotatedFields(TestClass.class);
+ assertEquals(8, annotatedFields.size()); // 8 annotated fields in TestClass
+
+ Map> byPurpose =
+ ModifiableVariableAnalyzer.groupFieldsByPurpose(TestClass.class);
+ assertTrue(byPurpose.containsKey(Purpose.LENGTH));
+ assertTrue(byPurpose.containsKey(Purpose.KEY_MATERIAL));
+ assertTrue(byPurpose.containsKey(Purpose.CONSTANT));
+ assertTrue(byPurpose.containsKey(Purpose.RANDOM));
+ assertTrue(byPurpose.containsKey(Purpose.PADDING));
+
+ List lengthFields =
+ ModifiableVariableAnalyzer.getFieldsByPurpose(TestClass.class, Purpose.LENGTH);
+ assertEquals(1, lengthFields.size());
+ assertEquals("length", lengthFields.get(0).getName());
+
+ List x509Fields =
+ ModifiableVariableAnalyzer.getFieldsByEncoding(TestClass.class, Encoding.X509);
+ assertEquals(1, x509Fields.size());
+ assertEquals("enhancedProperty", x509Fields.get(0).getName());
+
+ List unannotated =
+ ModifiableVariableAnalyzer.getUnannotatedModifiableVariables(TestClass.class);
+ assertEquals(1, unannotated.size());
+ assertEquals("unannotatedField", unannotated.get(0));
}
}