/*
* Copyright 2014 OpenRQ Team
*
* Licensed 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 net.fec.openrq;
import static org.junit.Assert.assertArrayEquals;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import net.fec.openrq.decoder.SourceBlockDecoder;
import net.fec.openrq.decoder.SourceBlockState;
import net.fec.openrq.encoder.SourceBlockEncoder;
import net.fec.openrq.parameters.FECParameters;
import net.fec.openrq.parameters.ParameterChecker;
import net.fec.openrq.util.math.ExtraMath;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/**
* Encodes a data object and decodes it, then checks if the decoded matches the original.
*/
@RunWith(Parameterized.class)
public final class DataIntegrityCheckTest {
private static int MAX_EXTRA_SYMBOLS;
private static Random RAND;
@BeforeClass
public static void initStaticParameters() {
MAX_EXTRA_SYMBOLS = 2;
RAND = TestingCommon.newSeededRandom();
}
@Parameters(name = "{0}")
public static Iterable<Object[]> getFECParams() {
final int[] Fs = TestingCommon.primeExponentialDistribution(2, 15);
final int[] Ks = TestingCommon.primeExponentialDistribution(10, 3);
final int[] Zs = TestingCommon.primeExponentialDistribution(2, 3);
final int N = 1;
// an estimation of how many sets of FEC parameters will be needed (not every combination is valid)
final List<Object[]> params = new ArrayList<>((Fs.length * Ks.length * Zs.length) / 2);
// fill the list with FEC parameters of many combinations
// start with the highest parameters so we can perceive immediately the maximum time per test
for (int f = Fs.length - 1; f >= 0; f--) {
final int F = Fs[f];
for (int k = Ks.length - 1; k >= 0; k--) {
final int K = Ks[k];
for (int z = Zs.length - 1; z >= 0; z--) {
final int Z = Zs[z];
final int T = ExtraMath.ceilDiv(F, K);
if (ParameterChecker.areValidFECParameters(F, T, Z, N)) {
params.add(new Object[] {FECParameters.newParameters(F, T, Z)});
}
}
}
}
System.out.printf("Testing data integrity with F=[%d, %d] K=[%d, %d] Z=[%d, %d] N=%d%n",
Fs[0], Fs[Fs.length - 1], Ks[0], Ks[Ks.length - 1], Zs[0], Zs[Zs.length - 1], N, N);
System.out.println("Testing " + 2 * params.size() + " data integrity tests...");
return params;
}
@Parameter(0)
public FECParameters fecParams;
@Test
public void checkDataWithSourceSymbols() {
final byte[] data = TestingCommon.randomBytes(fecParams.dataLengthAsInt(), RAND);
final ArrayDataEncoder enc = OpenRQ.newEncoder(data, fecParams);
final ArrayDataDecoder dec = OpenRQ.newDecoder(fecParams, 0);
for (SourceBlockEncoder sbEnc : enc.sourceBlockIterable()) {
final SourceBlockDecoder sbDec = dec.sourceBlock(sbEnc.sourceBlockNumber());
for (EncodingPacket srcPacket : sbEnc.sourcePacketsIterable()) {
sbDec.putEncodingPacket(srcPacket);
}
}
// compare the original and decoded data
assertArrayEquals(data, dec.dataArray());
}
@Test
public void checkDataWithRandomSymbols() {
final byte[] data = TestingCommon.randomBytes(fecParams.dataLengthAsInt(), RAND);
final ArrayDataEncoder enc = OpenRQ.newEncoder(data, fecParams);
final ArrayDataDecoder dec = OpenRQ.newDecoder(fecParams, 0);
for (SourceBlockEncoder sbEnc : enc.sourceBlockIterable()) {
final int K = sbEnc.numberOfSourceSymbols();
final Set<Integer> esis = TestingCommon.randomAnyESIs(RAND, K + MAX_EXTRA_SYMBOLS);
final Iterator<Integer> esiIter = esis.iterator();
SourceBlockState sbState = SourceBlockState.INCOMPLETE;
// this should loop K times
while (sbState == SourceBlockState.INCOMPLETE) {
final EncodingPacket packet = sbEnc.encodingPacket(esiIter.next());
final SourceBlockDecoder sbDec = dec.sourceBlock(packet.sourceBlockNumber());
sbState = sbDec.putEncodingPacket(packet);
}
// if the decoding failed, we need more symbols
while (sbState != SourceBlockState.DECODED && esiIter.hasNext()) {
final EncodingPacket packet = sbEnc.encodingPacket(esiIter.next());
final SourceBlockDecoder sbDec = dec.sourceBlock(packet.sourceBlockNumber());
sbState = sbDec.putEncodingPacket(packet);
}
// if the source block is still not decoded, then we ignore this test
Assume.assumeTrue(sbState == SourceBlockState.DECODED);
}
// compare the original and decoded data
assertArrayEquals(data, dec.dataArray());
}
}