@@ -105,6 +105,51 @@ func testSend(ctx context.Context, c *mockCluster, from uint64, to []uint64, msg
105105 }
106106}
107107
108+ // TestSplitSnapshotDataDoesNotMutateInput is a regression test for #3231.
109+ // Before the fix, splitSnapshotData did a shallow copy of the raft message
110+ // and re-sliced the shared Snapshot.Data on each iteration, shrinking the
111+ // original slice's capacity and eventually panicking with
112+ // "slice bounds out of range".
113+ func TestSplitSnapshotDataDoesNotMutateInput (t * testing.T ) {
114+ ctx := context .Background ()
115+
116+ // Build a MsgSnap whose Snapshot.Data clearly exceeds GRPCMaxMsgSize so
117+ // that the split loop runs multiple iterations (where the bug manifests).
118+ const dataSize = 3 * GRPCMaxMsgSize
119+ data := make ([]byte , dataSize )
120+ for i := range data {
121+ data [i ] = byte (i % (1 << 8 ))
122+ }
123+ m := raftpb.Message {
124+ Type : raftpb .MsgSnap ,
125+ From : 1 ,
126+ To : 2 ,
127+ Snapshot : & raftpb.Snapshot {
128+ Data : data ,
129+ Metadata : raftpb.SnapshotMetadata {
130+ Index : uint64 (len (data )),
131+ },
132+ },
133+ }
134+ origData := m .Snapshot .Data
135+ origLen , origCap := len (origData ), cap (origData )
136+
137+ msgs := splitSnapshotData (ctx , & m )
138+ require .Greater (t , len (msgs ), 1 , "data larger than GRPCMaxMsgSize must split into multiple chunks" )
139+
140+ // Chunks must reassemble to the original data.
141+ var assembled []byte
142+ for _ , msg := range msgs {
143+ assembled = append (assembled , msg .Message .Snapshot .Data ... )
144+ }
145+ assert .Equal (t , data , assembled )
146+
147+ // The input message's Snapshot.Data must be untouched (regression guard).
148+ assert .Equal (t , origLen , len (m .Snapshot .Data ))
149+ assert .Equal (t , origCap , cap (m .Snapshot .Data ))
150+ assert .Equal (t , data , m .Snapshot .Data )
151+ }
152+
108153func TestSend (t * testing.T ) {
109154 ctx , cancel := context .WithCancel (context .Background ())
110155 c := newCluster ()
0 commit comments