This is an automated email from the ASF dual-hosted git repository.

mimaison pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 8935db691dd KAFKA-20659: Reject unsupported MirrorMaker record 
versions at runtime (#22504)
8935db691dd is described below

commit 8935db691dd927d6352b51df6365f4787eae7642
Author: nileshkumar3 <[email protected]>
AuthorDate: Wed Jun 24 13:16:32 2026 -0500

    KAFKA-20659: Reject unsupported MirrorMaker record versions at runtime 
(#22504)
    
    
    Reviewers: Mickael Maison <[email protected]>
---
 .../apache/kafka/connect/mirror/Checkpoint.java    |  5 +-
 .../org/apache/kafka/connect/mirror/Heartbeat.java |  5 +-
 .../connect/mirror/MirrorRecordVersionTest.java    | 88 ++++++++++++++++++++++
 3 files changed, 96 insertions(+), 2 deletions(-)

diff --git 
a/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Checkpoint.java
 
b/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Checkpoint.java
index 3e0a2ee6177..3bfbca81bbc 100644
--- 
a/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Checkpoint.java
+++ 
b/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Checkpoint.java
@@ -19,6 +19,7 @@ package org.apache.kafka.connect.mirror;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
 import org.apache.kafka.clients.consumer.OffsetAndMetadata;
 import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.errors.UnsupportedVersionException;
 import org.apache.kafka.common.protocol.types.Field;
 import org.apache.kafka.common.protocol.types.Schema;
 import org.apache.kafka.common.protocol.types.Struct;
@@ -138,7 +139,9 @@ public class Checkpoint {
     }
 
     private static Schema valueSchema(short version) {
-        assert version == 0;
+        if (version != VERSION) {
+            throw new UnsupportedVersionException("Unsupported version " + 
version);
+        }
         return VALUE_SCHEMA_V0;
     }
 
diff --git 
a/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Heartbeat.java
 
b/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Heartbeat.java
index d63dfa70ff8..42bc666fdd7 100644
--- 
a/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Heartbeat.java
+++ 
b/connect/mirror-client/src/main/java/org/apache/kafka/connect/mirror/Heartbeat.java
@@ -17,6 +17,7 @@
 package org.apache.kafka.connect.mirror;
 
 import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.common.errors.UnsupportedVersionException;
 import org.apache.kafka.common.protocol.types.Field;
 import org.apache.kafka.common.protocol.types.Schema;
 import org.apache.kafka.common.protocol.types.Struct;
@@ -140,7 +141,9 @@ public class Heartbeat {
     }
 
     private static Schema valueSchema(short version) {
-        assert version == 0;
+        if (version != VERSION) {
+            throw new UnsupportedVersionException("Unsupported version " + 
version);
+        }
         return VALUE_SCHEMA_V0;
     }
 }
diff --git 
a/connect/mirror-client/src/test/java/org/apache/kafka/connect/mirror/MirrorRecordVersionTest.java
 
b/connect/mirror-client/src/test/java/org/apache/kafka/connect/mirror/MirrorRecordVersionTest.java
new file mode 100644
index 00000000000..628975d918f
--- /dev/null
+++ 
b/connect/mirror-client/src/test/java/org/apache/kafka/connect/mirror/MirrorRecordVersionTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.kafka.connect.mirror;
+
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.errors.UnsupportedVersionException;
+import org.apache.kafka.common.protocol.types.Schema;
+import org.apache.kafka.common.protocol.types.Struct;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class MirrorRecordVersionTest {
+
+    @Test
+    public void checkpointDeserializeRejectsUnsupportedValueVersion() {
+        Checkpoint checkpoint = new Checkpoint("group", new 
TopicPartition("topic", 0), 10L, 20L, "metadata");
+        ConsumerRecord<byte[], byte[]> record = new ConsumerRecord<>(
+                "checkpoints",
+                0,
+                0,
+                checkpoint.recordKey(),
+                checkpointValueWithVersion((short) (Checkpoint.VERSION + 1))
+        );
+
+        assertThrows(UnsupportedVersionException.class, () -> 
Checkpoint.deserializeRecord(record));
+    }
+
+    @Test
+    public void heartbeatDeserializeRejectsUnsupportedValueVersion() {
+        Heartbeat heartbeat = new Heartbeat("source", "target", 123L);
+        ConsumerRecord<byte[], byte[]> record = new ConsumerRecord<>(
+                "heartbeats",
+                0,
+                0,
+                heartbeat.recordKey(),
+                heartbeatValueWithVersion((short) (Heartbeat.VERSION + 1))
+        );
+
+        assertThrows(UnsupportedVersionException.class, () -> 
Heartbeat.deserializeRecord(record));
+    }
+
+    private static byte[] checkpointValueWithVersion(short version) {
+        Struct header = new Struct(Checkpoint.HEADER_SCHEMA);
+        header.set(Checkpoint.VERSION_KEY, version);
+        Struct value = new Struct(Checkpoint.VALUE_SCHEMA_V0);
+        value.set(Checkpoint.UPSTREAM_OFFSET_KEY, 10L);
+        value.set(Checkpoint.DOWNSTREAM_OFFSET_KEY, 20L);
+        value.set(Checkpoint.METADATA_KEY, "metadata");
+        return write(Checkpoint.HEADER_SCHEMA, header, 
Checkpoint.VALUE_SCHEMA_V0, value);
+    }
+
+    private static byte[] heartbeatValueWithVersion(short version) {
+        Struct header = new Struct(Heartbeat.HEADER_SCHEMA);
+        header.set(Heartbeat.VERSION_KEY, version);
+        Struct value = new Struct(Heartbeat.VALUE_SCHEMA_V0);
+        value.set(Heartbeat.TIMESTAMP_KEY, 123L);
+        return write(Heartbeat.HEADER_SCHEMA, header, 
Heartbeat.VALUE_SCHEMA_V0, value);
+    }
+
+    private static byte[] write(Schema headerSchema, Struct header, Schema 
valueSchema, Struct value) {
+        ByteBuffer buffer = ByteBuffer.allocate(headerSchema.sizeOf(header) + 
valueSchema.sizeOf(value));
+        headerSchema.write(buffer, header);
+        valueSchema.write(buffer, value);
+        buffer.flip();
+        byte[] result = new byte[buffer.remaining()];
+        buffer.get(result);
+        return result;
+    }
+}

Reply via email to