Fix random number generators in WebSocketUtil

Motivation:

Implementation of WebSocketUtil/randomNumber is incorrect and might violate
the API returning values > maximum specified.

Modifications:

* WebSocketUtil/randomNumber is reimplemented, the idea of the solution described
  in the comment in the code

* Implementation of WebSocketUtil/randomBytes changed to nextBytes method

* PlatformDependet.threadLocalRandom is used instead of Math.random to improve efficiency

* Added test cases to check random numbers generator

* To ensure corretness, we now assert that min < max when generating random number

Result:

WebSocketUtil/randomNumber always produces correct result.

Covers https://github.com/netty/netty/issues/8023
This commit is contained in:
Alexey Kachayev 2018-06-22 22:54:34 +03:00 committed by Norman Maurer
parent 9ffdec302e
commit fa4e28ba1c
3 changed files with 70 additions and 8 deletions

View File

@ -20,6 +20,7 @@ import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.PlatformDependent;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -105,11 +106,7 @@ final class WebSocketUtil {
*/
static byte[] randomBytes(int size) {
byte[] bytes = new byte[size];
for (int index = 0; index < size; index++) {
bytes[index] = (byte) randomNumber(0, 255);
}
PlatformDependent.threadLocalRandom().nextBytes(bytes);
return bytes;
}
@ -121,7 +118,29 @@ final class WebSocketUtil {
* @return A pseudo-random number
*/
static int randomNumber(int minimum, int maximum) {
return (int) (Math.random() * maximum + minimum);
assert minimum < maximum;
double fraction = PlatformDependent.threadLocalRandom().nextDouble();
// the idea here is that nextDouble gives us a random value
//
// 0 <= fraction <= 1
//
// the distance from min to max declared as
//
// dist = max - min
//
// satisfies the following
//
// min + dist = max
//
// taking into account
//
// 0 <= fraction * dist <= dist
//
// we've got
//
// min <= min + fraction * dist <= max
return (int) (minimum + fraction * (maximum - minimum));
}
/**

View File

@ -240,8 +240,8 @@ public abstract class WebSocketClientHandshakerTest {
}
};
byte[] data = new byte[24];
PlatformDependent.threadLocalRandom().nextBytes(data);
// use randomBytes helper from utils to check that it functions properly
byte[] data = WebSocketUtil.randomBytes(24);
// Create a EmbeddedChannel which we will use to encode a BinaryWebsocketFrame to bytes and so use these
// to test the actual handshaker.

View File

@ -0,0 +1,43 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project 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 io.netty.handler.codec.http.websocketx;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class WebSocketUtilTest {
// how many times do we want to run each random variable checker
private static final int NUM_ITERATIONS = 1000;
private static void assertRandomWithinBoundaries(int min, int max) {
int r = WebSocketUtil.randomNumber(min, max);
assertTrue(min <= r && r <= max);
}
@Test
public void testRandomNumberGenerator() {
int iteration = 0;
while (++iteration < NUM_ITERATIONS) {
assertRandomWithinBoundaries(0, 1);
assertRandomWithinBoundaries(0, 1);
assertRandomWithinBoundaries(-1, 1);
assertRandomWithinBoundaries(-1, 0);
}
}
}