package freenet.client.async;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import junit.framework.TestCase;
import freenet.client.ClientMetadata;
import freenet.client.HighLevelSimpleClientImpl;
import freenet.client.InsertContext;
import freenet.client.InsertException;
import freenet.client.Metadata;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.client.async.SplitFileInserterSegmentStorage.BlockInsert;
import freenet.client.async.SplitFileInserterSegmentStorage.MissingKeyException;
import freenet.client.async.SplitFileInserterStorage.Status;
import freenet.client.async.SplitFileInserterStorageTest.MyCallback;
import freenet.client.async.SplitFileInserterStorageTest.MyKeysFetchingLocally;
import freenet.client.events.SimpleEventProducer;
import freenet.crypt.CRCChecksumChecker;
import freenet.crypt.ChecksumChecker;
import freenet.crypt.DummyRandomSource;
import freenet.crypt.HashResult;
import freenet.crypt.HashType;
import freenet.crypt.MultiHashInputStream;
import freenet.crypt.RandomSource;
import freenet.keys.ClientKey;
import freenet.keys.FreenetURI;
import freenet.keys.Key;
import freenet.node.KeysFetchingLocally;
import freenet.node.LowLevelPutException;
import freenet.node.RequestClient;
import freenet.node.SendableInsert;
import freenet.node.SendableRequestItem;
import freenet.node.SendableRequestSender;
import freenet.support.CheatingTicker;
import freenet.support.DummyJobRunner;
import freenet.support.MemoryLimitedJobRunner;
import freenet.support.PooledExecutor;
import freenet.support.Ticker;
import freenet.support.WaitableExecutor;
import freenet.support.api.BucketFactory;
import freenet.support.api.LockableRandomAccessBuffer;
import freenet.support.api.LockableRandomAccessBufferFactory;
import freenet.support.io.ArrayBucketFactory;
import freenet.support.io.BucketTools;
import freenet.support.io.ByteArrayRandomAccessBufferFactory;
import freenet.support.io.FileUtil;
import freenet.support.io.FilenameGenerator;
import freenet.support.io.NullOutputStream;
import freenet.support.io.PersistentFileTracker;
import freenet.support.io.PooledFileRandomAccessBufferFactory;
import freenet.support.io.RAFInputStream;
import freenet.support.io.ReadOnlyRandomAccessBuffer;
import freenet.support.io.ResumeFailedException;
import freenet.support.io.TempBucketFactory;
import freenet.support.io.TrivialPersistentFileTracker;
public class ClientRequestSelectorTest extends TestCase {
final LockableRandomAccessBufferFactory smallRAFFactory = new ByteArrayRandomAccessBufferFactory();
final FilenameGenerator fg;
final PersistentFileTracker persistentFileTracker;
final LockableRandomAccessBufferFactory bigRAFFactory;
final BucketFactory smallBucketFactory;
final BucketFactory bigBucketFactory;
final File dir;
final InsertContext baseContext;
final WaitableExecutor executor;
final Ticker ticker;
final byte cryptoAlgorithm = Key.ALGO_AES_CTR_256_SHA256;
final byte[] cryptoKey;
final ChecksumChecker checker;
final MemoryLimitedJobRunner memoryLimitedJobRunner;
final PersistentJobRunner jobRunner;
final KeySalter salt = new KeySalter() {
@Override
public byte[] saltKey(Key key) {
return key.getRoutingKey();
}
};
private final FreenetURI URI;
public ClientRequestSelectorTest() throws IOException {
dir = new File("split-file-inserter-storage-test");
dir.mkdir();
executor = new WaitableExecutor(new PooledExecutor());
ticker = new CheatingTicker(executor);
RandomSource r = new DummyRandomSource(12345);
fg = new FilenameGenerator(r, true, dir, "freenet-test");
persistentFileTracker = new TrivialPersistentFileTracker(dir, fg);
bigRAFFactory = new PooledFileRandomAccessBufferFactory(fg, r);
smallBucketFactory = new ArrayBucketFactory();
bigBucketFactory = new TempBucketFactory(executor, fg, 0, 0, r, false, 0, null);
baseContext = HighLevelSimpleClientImpl.makeDefaultInsertContext(bigBucketFactory, new SimpleEventProducer());
cryptoKey = new byte[32];
r.nextBytes(cryptoKey);
checker = new CRCChecksumChecker();
memoryLimitedJobRunner = new MemoryLimitedJobRunner(9*1024*1024L, 20, executor);
jobRunner = new DummyJobRunner(executor, null);
URI = FreenetURI.generateRandomCHK(r);
}
class MyCallback implements SplitFileInserterStorageCallback {
MyCallback(SendableInsert sender) {
this.sender = sender;
}
private boolean finishedEncode;
private boolean hasKeys;
private boolean succeededInsert;
private InsertException failed;
private Metadata metadata;
private final SendableInsert sender;
@Override
public synchronized void onFinishedEncode() {
finishedEncode = true;
notifyAll();
}
@Override
public synchronized void onHasKeys() {
hasKeys = true;
notifyAll();
}
@Override
public void encodingProgress() {
// Ignore.
}
private void checkFailed() throws InsertException {
if(failed != null)
throw failed;
}
public synchronized void waitForFinishedEncode() throws InsertException {
while(!finishedEncode) {
checkFailed();
try {
wait();
} catch (InterruptedException e) {
// Ignore.
}
}
}
public synchronized void waitForHasKeys() throws InsertException {
while(!hasKeys) {
checkFailed();
try {
wait();
} catch (InterruptedException e) {
// Ignore.
}
}
}
@Override
public synchronized void onSucceeded(Metadata metadata) {
succeededInsert = true;
this.metadata = metadata;
notifyAll();
}
public synchronized Metadata waitForSucceededInsert() throws InsertException {
while(!succeededInsert) {
checkFailed();
try {
wait();
} catch (InterruptedException e) {
// Ignore.
}
}
return metadata;
}
@Override
public synchronized void onFailed(InsertException e) {
failed = e;
notifyAll();
}
@Override
public void onInsertedBlock() {
// Ignore.
}
@Override
public void clearCooldown() {
// Ignore.
}
public synchronized boolean hasFailed() {
return failed != null;
}
public synchronized boolean hasFinishedEncode() {
return finishedEncode;
}
}
private HashResult[] getHashes(LockableRandomAccessBuffer data) throws IOException {
InputStream is = new RAFInputStream(data, 0, data.size());
MultiHashInputStream hashStream = new MultiHashInputStream(is, HashType.SHA256.bitmask);
FileUtil.copy(is, new NullOutputStream(), data.size());
is.close();
return hashStream.getResults();
}
private LockableRandomAccessBuffer generateData(Random random, long size,
LockableRandomAccessBufferFactory smallRAFFactory) throws IOException {
LockableRandomAccessBuffer thing = smallRAFFactory.makeRAF(size);
BucketTools.fill(thing, random, 0, size);
return new ReadOnlyRandomAccessBuffer(thing);
}
class NullSendableInsert extends SendableInsert {
public NullSendableInsert(boolean persistent, boolean realTimeFlag) {
super(persistent, realTimeFlag);
}
@Override
public void onSuccess(SendableRequestItem keyNum, ClientKey key, ClientContext context) {
// Ignore
}
@Override
public void onFailure(LowLevelPutException e, SendableRequestItem keyNum,
ClientContext context) {
// Ignore
}
@Override
public boolean canWriteClientCache() {
return false;
}
@Override
public boolean localRequestOnly() {
return false;
}
@Override
public boolean forkOnCacheable() {
return false;
}
@Override
public void onEncode(SendableRequestItem token, ClientKey key, ClientContext context) {
// Ignore
}
@Override
public boolean isEmpty() {
return false;
}
@Override
protected void innerOnResume(ClientContext context) throws InsertException,
ResumeFailedException {
// Ignore
}
@Override
public short getPriorityClass() {
return 0;
}
@Override
public SendableRequestItem chooseKey(KeysFetchingLocally keys, ClientContext context) {
return null;
}
@Override
public long countAllKeys(ClientContext context) {
return 0;
}
@Override
public long countSendableKeys(ClientContext context) {
return 0;
}
@Override
public SendableRequestSender getSender(ClientContext context) {
return null;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public RequestClient getClient() {
return null;
}
@Override
public ClientRequester getClientRequest() {
return null;
}
@Override
public boolean isSSK() {
return false;
}
}
public void testSmallSplitfileChooseCompletion() throws IOException, InsertException, MissingKeyException {
Random r = new Random(12121);
long size = 65536; // Exact multiple, so no last block
LockableRandomAccessBuffer data = generateData(r, size, smallRAFFactory);
HashResult[] hashes = getHashes(data);
NullSendableInsert insert = new NullSendableInsert(false, false);
MyCallback cb = new MyCallback(insert);
InsertContext context = baseContext.clone();
context.maxInsertRetries = 2;
ClientRequestSelector keys = new ClientRequestSelector(true, false, false, null);
SplitFileInserterStorage storage = new SplitFileInserterStorage(data, size, cb, null,
new ClientMetadata(), false, null, smallRAFFactory, false, context,
cryptoAlgorithm, cryptoKey, null, hashes, smallBucketFactory, checker,
r, memoryLimitedJobRunner, jobRunner, ticker, keys, false, 0, 0, 0, 0);
storage.start();
cb.waitForFinishedEncode();
assertEquals(storage.segments.length, 1);
SplitFileInserterSegmentStorage segment = storage.segments[0];
assertEquals(segment.dataBlockCount, 2);
assertEquals(segment.checkBlockCount, 3);
assertEquals(segment.crossCheckBlockCount, 0);
assertEquals(storage.getStatus(), Status.ENCODED);
boolean[] chosenBlocks = new boolean[segment.totalBlockCount];
// Choose and fail all blocks.
for(int i=0;i<segment.totalBlockCount;i++) {
BlockInsert chosen = segment.chooseBlock();
assertTrue(chosen != null);
keys.addRunningInsert(chosen);
assertFalse(chosenBlocks[chosen.blockNumber]);
chosenBlocks[chosen.blockNumber] = true;
segment.onFailure(chosen.blockNumber, new InsertException(InsertExceptionMode.ROUTE_NOT_FOUND));
}
BlockInsert chosen = segment.chooseBlock();
assertTrue(chosen == null);
for(int i=0;i<segment.totalBlockCount;i++)
keys.removeRunningInsert(new BlockInsert(segment, i));
// Choose and succeed all blocks.
chosenBlocks = new boolean[segment.totalBlockCount];
for(int i=0;i<segment.totalBlockCount;i++) {
chosen = segment.chooseBlock();
keys.addRunningInsert(chosen);
assertTrue(chosen != null);
assertFalse(chosenBlocks[chosen.blockNumber]);
chosenBlocks[chosen.blockNumber] = true;
segment.onInsertedBlock(chosen.blockNumber, segment.encodeBlock(chosen.blockNumber).getClientKey());
}
cb.waitForSucceededInsert();
assertEquals(storage.getStatus(), Status.SUCCEEDED);
}
}