package peergos.shared.user.fs.erasure;
import org.junit.*;
import peergos.shared.util.StringUtils;
import java.math.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class ErrorTests {
public static class ErasureParameters {
@org.junit.Test
public void findAcceptableErasureParameters() {
int totalUserSizeInMB = 10000;
int nChunks = totalUserSizeInMB / 5;
Predicate<BigDecimal> acceptableFailure = pr -> pr.doubleValue() < 1.0/nChunks; // Expect not to lose a single chunk
Stream.of(60).forEach(n -> IntStream.range(1, 6).map(i -> i*n/6)
.forEach(k -> Stream.of(0.5, 0.7, 0.8, 0.9).forEach(p -> {
BigDecimal prFail = probabilityFailure(n, k, p);
if (acceptableFailure.test(prFail))
System.out.println(StringUtils.format("%d, %d, %.2f -> %.15f\n", n, k, p, prFail));
}
)
)
);
double p = 0.5;
int f = 40, e = 10;
int n = f + 2*e, k = f + e;
double min_p = 0.0, max_p = 1.0;
while (true) {
if (acceptableFailure.test(probabilityFailure(n, k, p))) {
max_p = p;
p = (p + min_p) / 2;
} else {
min_p = p;
p = (1 + p) / 2;
}
if (Math.abs(max_p - min_p) < 0.01)
break;
}
System.out.println(StringUtils.format("%d, %d, %.2f -> %.15f\n", n, k, p, probabilityFailure(n, k, p)));
}
}
/*
* The probability of data loss using a k of n erasure code with each fragment having pr(correct) = p
* */
public static BigDecimal probabilityFailure(int n, int k, double p) {
List<BigDecimal> collect = IntStream.range(0, k)
.mapToObj(i -> new BigDecimal(p).pow(i).multiply(new BigDecimal(1 - p).pow(n - i)).multiply(choose(n, i))).collect(Collectors.toList());
return collect.stream()
.reduce(new BigDecimal(0), (a, b) -> a.add(b));
}
static BigInteger[][] choose = new BigInteger[200][200];
static {
for (int i=0; i < choose.length; i++) {
choose[i][0] = BigInteger.valueOf(1);
choose[i][i] = BigInteger.valueOf(1);
}
for (int i=1; i < choose.length; i++)
for (int j=1; j < i; j++)
choose[i][j] = choose[i-1][j-1].add(choose[i-1][j]);
}
private static BigDecimal choose(int n, int k) {
return new BigDecimal(choose[n][k]);
}
@Test
public void recoverFromErrors() {
// 40 -> 128 KiB fragments which is nice
recoverFromerrors(40, 30);
}
@Test
public void standardRecovery() {
recoverFromerrors(40, 10);
}
public void recoverFromerrors(int fragments, int maxErrors) {
// this is a fragments + maxErrors of fragments + 2*maxErrors erasure scheme
byte[] original = new byte[5 * 1024 * 1024];
new Random().nextBytes(original);
IntStream.range(0, maxErrors+1).forEach(e -> recover(original, fragments, maxErrors, e));
IntStream.range(maxErrors + 1, 2*maxErrors).forEach(e ->
{
try {
recover(original, fragments, maxErrors, e);
throw new RuntimeException("Should fail with this many errors!");
} catch (IllegalStateException err) {}
}
);
}
public void recover(byte[] original, int fragments, int maxErrors, int actualErrors) {
byte[][] encoded = Erasure.split(original, fragments, maxErrors);
Set<Integer> done = new HashSet<>();
while (actualErrors - done.size() > 0) {
int index = new Random().nextInt(encoded.length);
if (!done.contains(index)) {
done.add(index);
encoded[index] = new byte[encoded[index].length];
}
}
byte[] recovered = Erasure.recombine(encoded, 5 * 1024 * 1024, fragments, maxErrors);
if (!Arrays.equals(original, recovered))
throw new IllegalStateException("Different result from original with "+actualErrors+" errors!");
System.out.println();
}
}