diff --git a/changelog/unreleased/SOLR-18098-fix-replication-exact-mb-sizes.yml b/changelog/unreleased/SOLR-18098-fix-replication-exact-mb-sizes.yml
new file mode 100644
index 00000000000..9729678a651
--- /dev/null
+++ b/changelog/unreleased/SOLR-18098-fix-replication-exact-mb-sizes.yml
@@ -0,0 +1,7 @@
+title: Fix replication failure for files with exact MB sizes
+type: fixed
+authors:
+ - name: Shubham Ranjan
+links:
+ - name: SOLR-18098
+ url: https://issues.apache.org/jira/browse/SOLR-18098
diff --git a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
index d83c581a2c4..3695dd0bb71 100644
--- a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
+++ b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
@@ -1752,14 +1752,28 @@ private int fetchPackets(FastInputStream fis) throws Exception {
aborted = true;
throw new ReplicationHandlerException("User aborted replication");
}
- long checkSumServer = -1;
fis.readFully(intbytes);
// read the size of the packet
int packetSize = readInt(intbytes);
+
+ // EOF marker: size=0 with no trailing data (no checksum follows)
+ if (packetSize <= 0 && fis.peek() == -1) {
+ continue;
+ }
+
+ // read the checksum (all data packets have checksums, including zero-length ones)
+ long checkSumServer = -1;
+ if (checksum != null) {
+ fis.readFully(longbytes);
+ checkSumServer = readLong(longbytes);
+ }
+
+ // zero-length data packet: checksum already consumed, skip to next packet
if (packetSize <= 0) {
continue;
}
+
// TODO consider recoding the remaining logic to not use/need buf[]; instead use the
// internal buffer of fis
if (buf.length < packetSize) {
@@ -1767,11 +1781,6 @@ private int fetchPackets(FastInputStream fis) throws Exception {
// that too
buf = new byte[packetSize];
}
- if (checksum != null) {
- // read the checksum
- fis.readFully(longbytes);
- checkSumServer = readLong(longbytes);
- }
// then read the packet of bytes
fis.readFully(buf, 0, packetSize);
// compare the checksum as sent from the leader
diff --git a/solr/core/src/test/org/apache/solr/handler/IndexFetcherPacketProtocolTest.java b/solr/core/src/test/org/apache/solr/handler/IndexFetcherPacketProtocolTest.java
new file mode 100644
index 00000000000..c26aa738b5b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/IndexFetcherPacketProtocolTest.java
@@ -0,0 +1,398 @@
+/*
+ * 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.solr.handler;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IOContext;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.FastInputStream;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.DirectoryFactory;
+import org.apache.solr.handler.admin.api.CoreReplication;
+import org.apache.solr.handler.admin.api.ReplicationAPIBase;
+import org.apache.solr.request.SolrQueryRequestBase;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests the replication packet protocol between DirectoryFileStream (sender) and
+ * IndexFetcher.FileFetcher.fetchPackets (receiver).
+ *
+ *
The packet protocol is:
+ *
+ *
+ *
Data packet: {@code int(size) + long(checksum) + byte[size]}
+ *
EOF marker: {@code int(0)} alone (no checksum)
+ *
+ *
+ *
These tests verify correct handling of:
+ *
+ *
+ *
Files that are exact multiples of PACKET_SZ (1 MB) - triggers zero-length data packets
+ *
Files of arbitrary sizes - boundary conditions and edge cases
+ *