View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.core.source32;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.ObjectInputStream;
22  import java.io.ObjectOutputStream;
23  import java.io.Serializable;
24  import java.util.Random;
25  
26  import org.apache.commons.rng.RandomProviderState;
27  import org.apache.commons.rng.core.RandomAssert;
28  import org.apache.commons.rng.core.RandomProviderDefaultState;
29  import org.apache.commons.rng.core.util.NumberFactory;
30  import org.junit.jupiter.api.Assertions;
31  import org.junit.jupiter.api.Test;
32  
33  class JDKRandomTest {
34      /**
35       * A class that is Serializable.
36       * It contains member fields so there is something to serialize and malicious
37       * deserialization code.
38       */
39      static class SerializableTestObject implements Serializable {
40          private static final long serialVersionUID = 1L;
41  
42          private int state0;
43          private double state1;
44          private long state2;
45          private boolean state3;
46  
47          /**
48           * This simulates doing something malicious when deserializing.
49           *
50           * @param input Input stream.
51           * @throws IOException if an error occurs.
52           * @throws ClassNotFoundException if an error occurs.
53           */
54          private void readObject(ObjectInputStream input)
55                  throws IOException,
56                         ClassNotFoundException {
57              Assertions.fail("*** Malicious code ***. This should not be run during the test");
58          }
59      }
60  
61      @Test
62      void testReferenceCode() {
63          final long refSeed = -1357111213L;
64          final JDKRandom rng = new JDKRandom(refSeed);
65          final Random jdk = new Random(refSeed);
66  
67          // This is a trivial test since "JDKRandom" delegates to "Random".
68  
69          final int numRepeats = 1000;
70          for (int[] r = {0}; r[0] < numRepeats; r[0]++) {
71              Assertions.assertEquals(jdk.nextInt(), rng.nextInt(), () -> r[0] + " nextInt");
72          }
73      }
74  
75      /**
76       * Test the state can be used to restore a new instance that has not previously had a call
77       * to save the state.
78       */
79      @Test
80      void testRestoreToNewInstance()  {
81          final long seed = 8796746234L;
82          final JDKRandom rng1 = new JDKRandom(seed);
83          final JDKRandom rng2 = new JDKRandom(seed + 1);
84  
85          // Ensure different
86          final int numRepeats = 10;
87          for (int[] r = {0}; r[0] < numRepeats; r[0]++) {
88              Assertions.assertNotEquals(rng1.nextInt(), rng2.nextInt(), () -> r[0] + " nextInt");
89          }
90  
91          final RandomProviderState state = rng1.saveState();
92          // This second instance will not know the state size required to write
93          // java.util.Random to serialized form. This is only known when the saveState
94          // method is called.
95          rng2.restoreState(state);
96  
97          // Ensure the same
98          RandomAssert.assertNextIntEquals(numRepeats, rng1, rng2);
99      }
100 
101     /**
102      * Test the deserialization code identifies bad states that do not contain a Random instance.
103      * This test exercises the code that uses a custom deserialization ObjectInputStream.
104      *
105      * @throws IOException Signals that an I/O exception has occurred.
106      */
107     @Test
108     void testRestoreWithInvalidClass() throws IOException  {
109         // Serialize something
110         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
111         try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
112             oos.writeObject(new SerializableTestObject());
113         }
114 
115         // Compose the size with the state.
116         // This is what is expected by the JDKRandom class.
117         final byte[] state = bos.toByteArray();
118         final int stateSize = state.length;
119         final byte[] sizeAndState = new byte[4 + stateSize];
120         System.arraycopy(NumberFactory.makeByteArray(stateSize), 0, sizeAndState, 0, 4);
121         System.arraycopy(state, 0, sizeAndState, 4, stateSize);
122 
123         final RandomProviderDefaultState dummyState = new RandomProviderDefaultState(sizeAndState);
124 
125         final JDKRandom rng = new JDKRandom(13L);
126         Assertions.assertThrows(IllegalStateException.class, () -> rng.restoreState(dummyState));
127     }
128 }