diff --git a/pom.xml b/pom.xml
index 9e848cb..3a0dbf7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -127,6 +127,16 @@
6.1.0
provided
+
+ net.bytebuddy
+ byte-buddy
+ LATEST
+
+
+ net.bytebuddy
+ byte-buddy-agent
+ LATEST
+
diff --git a/src/main/java/org/openautonomousconnection/protocol/annotations/ProtocolInfo.java b/src/main/java/org/openautonomousconnection/protocol/annotations/ProtocolInfo.java
index e908cf3..d357f02 100644
--- a/src/main/java/org/openautonomousconnection/protocol/annotations/ProtocolInfo.java
+++ b/src/main/java/org/openautonomousconnection/protocol/annotations/ProtocolInfo.java
@@ -2,9 +2,13 @@ package org.openautonomousconnection.protocol.annotations;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Annotation to provide metadata about protocol handlers or classes.
*/
+@Retention(RetentionPolicy.RUNTIME)
public @interface ProtocolInfo {
/**
diff --git a/src/main/java/org/openautonomousconnection/protocol/annotations/processing/CallTracker.java b/src/main/java/org/openautonomousconnection/protocol/annotations/processing/CallTracker.java
new file mode 100644
index 0000000..eeb0be9
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/annotations/processing/CallTracker.java
@@ -0,0 +1,83 @@
+package org.openautonomousconnection.protocol.annotations.processing;
+
+import dev.unlegitdqrk.unlegitlibrary.reflections.GenericReflectClass;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+
+import javax.annotation.Nullable;
+import java.lang.annotation.Annotation;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+
+public class CallTracker extends GenericReflectClass {
+ private static final AtomicReference> atomicClass = new AtomicReference<>();
+
+ public CallTracker(CallInterceptor interceptor) {
+ super();
+
+ atomicClass.set(this.persistentClass);
+ }
+
+ public static void premain(String agentArgs, Instrumentation inst) {
+ ByteBuddyAgent.install();
+
+ new AgentBuilder.Default()
+ .type(any()) // instrument all classes, you can restrict here
+ .transform((builder, type, classLoader, module, protectionDomain) ->
+ builder.visit(Advice.to(CallInterceptor.class).on(isMethod()))
+ ).installOn(inst);
+ }
+
+ public abstract static class CallInterceptor {
+ private static Set interceptors = new HashSet<>();
+
+ public CallInterceptor() {
+ interceptors.add(this);
+ }
+
+ /**
+ * Code executed on any method call
+ */
+ public abstract void onCall(Method method, @Nullable StackTraceElement callerMethod);
+
+ @Advice.OnMethodEnter
+ static void intercept(@Advice.Origin Method method) {
+
+ for(CallInterceptor interceptor : interceptors) {
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+
+ if (stack.length <= 3)
+ return;
+
+ StackTraceElement caller = stack[3];
+
+ interceptor.onCall(method, caller);
+ }
+
+// if (method.isAnnotationPresent(atomicClass.get())) {
+// StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+// // stack[0] = getStackTrace
+// // stack[1] = intercept
+// // stack[2] = Advice dispatcher
+// // stack[3+] = your actual caller
+// if (stack.length <= 3)
+// return;
+//
+//
+//
+// StackTraceElement caller = stack[3];
+//
+// System.out.println("Annotated method " + method.getName()
+// + " was called by " + caller.getClassName() + "." + caller.getMethodName());
+//
+// }
+ }
+ }
+}
diff --git a/src/main/java/org/openautonomousconnection/protocol/annotations/processing/ProtocolInfoProcessing.java b/src/main/java/org/openautonomousconnection/protocol/annotations/processing/ProtocolInfoProcessing.java
new file mode 100644
index 0000000..4e32ace
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/annotations/processing/ProtocolInfoProcessing.java
@@ -0,0 +1,99 @@
+// Author: maple
+// date: 9/29/25
+
+package org.openautonomousconnection.protocol.annotations.processing;
+
+import dev.unlegitdqrk.unlegitlibrary.reflections.annotation.processing.AnnotationProcessor;
+import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
+import org.openautonomousconnection.protocol.exceptions.IncompatibleProtocolSideException;
+import org.openautonomousconnection.protocol.versions.ProtocolVersion;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Process ProtocolInfo annotation and throw exception on mismatching annotation and ProtocolSide
+ */
+public class ProtocolInfoProcessing extends AnnotationProcessor {
+
+ private final CallTracker tracker;
+
+ private final AtomicReference> methodReferences = new AtomicReference<>();
+ private final AtomicReference>> typeReferences = new AtomicReference<>();
+
+ public ProtocolInfoProcessing() {
+ super("org.openautonomousconnection.protocol");
+
+ this.process();
+
+ this.methodReferences.set(this.annotatedMethods);
+
+ this.typeReferences.set(this.annotatedTypes);
+
+ this.tracker = new CallTracker<>(new CallTracker.CallInterceptor() {
+ @Override
+ public void onCall(Method method, @Nullable StackTraceElement callerMethod) {
+ ProtocolVersion.ProtocolSide side, callerSide;
+
+ Object o;
+
+ if((o = methodGetByName(callerMethod.getMethodName())) != null)
+ callerSide = ((Method) o).getAnnotation(ProtocolInfo.class).protocolSide();
+ else if((o = typeHasAnnotation(callerMethod.getClassName())) != null)
+ callerSide = ((Class>) o).getAnnotation(ProtocolInfo.class).protocolSide();
+ else
+ return;
+
+
+ if(methodReferences.get().contains(method))
+ side = method.getAnnotation(ProtocolInfo.class).protocolSide();
+
+ else if(typeReferences.get().contains(method.getDeclaringClass()))
+ side = method.getDeclaringClass().getAnnotation(ProtocolInfo.class).protocolSide();
+ else
+ return;
+
+ if(callerSide.equals(ProtocolVersion.ProtocolSide.CLIENT) &&
+ !side.equals(callerSide))
+ throw new IncompatibleProtocolSideException(callerSide, side);
+
+
+
+ }
+ });
+ }
+
+ private Method methodGetByName(String methodName) {
+ for(Method method : this.annotatedMethods)
+ if(method.getName().equals(methodName))
+ return method;
+ return null;
+ }
+
+ private Class> typeHasAnnotation(String typeName) {
+ for(Class> type : this.annotatedTypes)
+ if(type.getName().equals(typeName))
+ return type;
+ return null;
+ }
+
+ @Override
+ protected void processType(Class> type) {
+ }
+
+ @Override
+ protected void processMethod(Method method) {
+ }
+
+ @Override
+ protected void processField(Field field) {
+ }
+
+ @Override
+ protected void processConstructor(Constructor constructor) {
+ }
+}
diff --git a/src/main/java/org/openautonomousconnection/protocol/exceptions/IncompatibleProtocolSideException.java b/src/main/java/org/openautonomousconnection/protocol/exceptions/IncompatibleProtocolSideException.java
new file mode 100644
index 0000000..e871ef3
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/exceptions/IncompatibleProtocolSideException.java
@@ -0,0 +1,12 @@
+package org.openautonomousconnection.protocol.exceptions;
+
+import org.openautonomousconnection.protocol.versions.ProtocolVersion;
+
+/**
+ * Exception thrown when an unsupported protocol side method is called.
+ */
+public class IncompatibleProtocolSideException extends RuntimeException {
+ public IncompatibleProtocolSideException(ProtocolVersion.ProtocolSide callerSide, ProtocolVersion.ProtocolSide side) {
+ super(callerSide.name() + " is incompatible with called method of ProtocolSide " + side.name());
+ }
+}