cache: add cache server
This commit is contained in:
@@ -53,7 +53,7 @@ import net.runelite.cache.fs.Store;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CacheClient
|
||||
public class CacheClient implements AutoCloseable
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CacheClient.class);
|
||||
|
||||
@@ -63,28 +63,30 @@ public class CacheClient
|
||||
private static final int CLIENT_REVISION = 139;
|
||||
|
||||
private final Store store; // store cache will be written to
|
||||
private final String host;
|
||||
private final int clientRevision;
|
||||
|
||||
private ClientState state;
|
||||
|
||||
private EventLoopGroup group = new NioEventLoopGroup(1);
|
||||
private final EventLoopGroup group = new NioEventLoopGroup(1);
|
||||
private Channel channel;
|
||||
|
||||
private CompletableFuture<Integer> handshakeFuture;
|
||||
private Queue<PendingFileRequest> requests = new ArrayDeque<>();
|
||||
private final Queue<PendingFileRequest> requests = new ArrayDeque<>();
|
||||
|
||||
public CacheClient(Store store)
|
||||
{
|
||||
this(store, CLIENT_REVISION);
|
||||
this(store, HOST, CLIENT_REVISION);
|
||||
}
|
||||
|
||||
public CacheClient(Store store, int clientRevision)
|
||||
public CacheClient(Store store, String host, int clientRevision)
|
||||
{
|
||||
this.store = store;
|
||||
this.host = host;
|
||||
this.clientRevision = clientRevision;
|
||||
}
|
||||
|
||||
public void connect() throws InterruptedException
|
||||
public void connect()
|
||||
{
|
||||
Bootstrap b = new Bootstrap();
|
||||
b.group(group)
|
||||
@@ -102,7 +104,7 @@ public class CacheClient
|
||||
});
|
||||
|
||||
// Start the client.
|
||||
ChannelFuture f = b.connect(HOST, PORT).sync();
|
||||
ChannelFuture f = b.connect(host, PORT).syncUninterruptibly();
|
||||
channel = f.channel();
|
||||
}
|
||||
|
||||
@@ -151,6 +153,7 @@ public class CacheClient
|
||||
logger.info("Client is now connected!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
channel.close().syncUninterruptibly();
|
||||
|
||||
@@ -72,6 +72,12 @@ public class CacheClientHandler extends ChannelInboundHandlerAdapter
|
||||
}
|
||||
else if (state == ClientState.CONNECTED)
|
||||
{
|
||||
if (buffer.readableBytes() < 8)
|
||||
{
|
||||
logger.trace("Connected, but not enough data yet to read header");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuf copy = buffer.slice();
|
||||
|
||||
int index = copy.readUnsignedByte();
|
||||
|
||||
@@ -147,7 +147,7 @@ public class Archive
|
||||
|
||||
if (this.crc != res.crc)
|
||||
{
|
||||
logger.warn("crc mismatch for archive {}", this);
|
||||
logger.warn("crc mismatch for archive {}", this.getArchiveId());
|
||||
}
|
||||
|
||||
if (this.getWhirlpool() != null && !Arrays.equals(this.getWhirlpool(), res.whirlpool))
|
||||
|
||||
@@ -377,8 +377,8 @@ public class DataFile implements Closeable
|
||||
DataFileReadResult res = new DataFileReadResult();
|
||||
res.data = data;
|
||||
res.revision = revision;
|
||||
int length = revision != -1 ? b.length - 2 : b.length;
|
||||
res.crc = crc32.getHash();
|
||||
int length = revision != -1 ? b.length - 2 : b.length;
|
||||
res.whirlpool = Whirlpool.getHash(b, length);
|
||||
res.compression = compression;
|
||||
return res;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
package net.runelite.cache.fs;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -35,6 +37,8 @@ import java.util.Objects;
|
||||
import net.runelite.cache.util.Djb2;
|
||||
import net.runelite.cache.io.InputStream;
|
||||
import net.runelite.cache.io.OutputStream;
|
||||
import net.runelite.cache.util.Crc32;
|
||||
import net.runelite.cache.util.Whirlpool;
|
||||
import net.runelite.cache.util.XteaKeyManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -195,6 +199,52 @@ public class Index implements Closeable
|
||||
return null;
|
||||
}
|
||||
|
||||
public void rebuildCrc() throws IOException
|
||||
{
|
||||
for (Archive a : archives)
|
||||
{
|
||||
assert this.index.getIndexFileId() == this.id;
|
||||
|
||||
int rev; // used for determining what part of compressedData to crc
|
||||
byte[] compressedData;
|
||||
|
||||
if (a.getData() != null)
|
||||
{
|
||||
compressedData = a.getData(); // data was never decompressed or loaded
|
||||
rev = -1; // assume that this data has no revision?
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] fileData = a.saveContents();
|
||||
rev = a.getRevision();
|
||||
compressedData = DataFile.compress(fileData, a.getCompression(), a.getRevision(), null);
|
||||
}
|
||||
|
||||
int length = rev != -1 ? compressedData.length - 2 : compressedData.length;
|
||||
Crc32 crc32 = new Crc32();
|
||||
crc32.update(compressedData, 0, length);
|
||||
|
||||
int crc = crc32.getHash();
|
||||
byte[] whirlpool = Whirlpool.getHash(compressedData, length);
|
||||
|
||||
a.setCrc(crc);
|
||||
a.setWhirlpool(whirlpool);
|
||||
}
|
||||
|
||||
Crc32 crc = new Crc32();
|
||||
byte[] indexData = this.writeIndexData();
|
||||
|
||||
ByteBuf b = Unpooled.buffer(5, 5);
|
||||
b.writeByte((byte) CompressionType.NONE);
|
||||
b.writeInt(indexData.length);
|
||||
|
||||
crc.update(b.array(), 0, 5); // crc includes compression type and length
|
||||
crc.update(indexData, 0, indexData.length);
|
||||
|
||||
int hash = crc.getHash();
|
||||
this.setCrc(hash);
|
||||
}
|
||||
|
||||
public void load() throws IOException
|
||||
{
|
||||
logger.trace("Loading index {}", id);
|
||||
@@ -221,6 +271,7 @@ public class Index implements Closeable
|
||||
|
||||
public void save() throws IOException
|
||||
{
|
||||
// This updates archive CRCs for writeIndexData
|
||||
saveArchives();
|
||||
|
||||
byte[] data = this.writeIndexData();
|
||||
|
||||
@@ -132,6 +132,16 @@ public class Store implements Closeable
|
||||
indexes.remove(index);
|
||||
}
|
||||
|
||||
/*
|
||||
* we rebuild data differently, so the CRCs aren't right.
|
||||
* rebuild them.
|
||||
*/
|
||||
public void rebuildCrc() throws IOException
|
||||
{
|
||||
for (Index i : indexes)
|
||||
i.rebuildCrc();
|
||||
}
|
||||
|
||||
public void load() throws IOException
|
||||
{
|
||||
for (Index i : indexes)
|
||||
|
||||
104
cache/src/main/java/net/runelite/cache/server/CacheServer.java
vendored
Normal file
104
cache/src/main/java/net/runelite/cache/server/CacheServer.java
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.cache.server;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import net.runelite.cache.fs.Store;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CacheServer implements AutoCloseable
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CacheServer.class);
|
||||
|
||||
private static final int PORT = 43594;
|
||||
|
||||
private final EventLoopGroup group = new NioEventLoopGroup(1);
|
||||
|
||||
private Channel channel;
|
||||
|
||||
private final Store store;
|
||||
private final int revision;
|
||||
|
||||
public CacheServer(Store store, int revision)
|
||||
{
|
||||
this.store = store;
|
||||
this.revision = revision;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.group(group)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>()
|
||||
{
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception
|
||||
{
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
|
||||
p.addLast(new CacheServerHandler(CacheServer.this));
|
||||
}
|
||||
});
|
||||
|
||||
ChannelFuture f = b.bind(PORT).syncUninterruptibly();
|
||||
channel = f.channel();
|
||||
|
||||
logger.info("Server is now listening on {}", PORT);
|
||||
}
|
||||
|
||||
public void waitForClose()
|
||||
{
|
||||
channel.closeFuture().awaitUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
channel.close().syncUninterruptibly();
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
|
||||
public int getRevision()
|
||||
{
|
||||
return revision;
|
||||
}
|
||||
|
||||
public Store getStore()
|
||||
{
|
||||
return store;
|
||||
}
|
||||
}
|
||||
250
cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java
vendored
Normal file
250
cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.cache.server;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import net.runelite.cache.downloader.requests.ConnectionInfo;
|
||||
import net.runelite.cache.downloader.requests.HelloHandshake;
|
||||
import net.runelite.cache.fs.Archive;
|
||||
import net.runelite.cache.fs.CompressionType;
|
||||
import net.runelite.cache.fs.DataFile;
|
||||
import net.runelite.cache.fs.Index;
|
||||
import net.runelite.cache.fs.Store;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CacheServerHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CacheServerHandler.class);
|
||||
|
||||
private final CacheServer server;
|
||||
private final Store store;
|
||||
|
||||
private ClientState state = ClientState.HANDSHAKING;
|
||||
|
||||
public CacheServerHandler(CacheServer server)
|
||||
{
|
||||
this.server = server;
|
||||
this.store = server.getStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception
|
||||
{
|
||||
System.out.println(ByteBufUtil.prettyHexDump(buf));
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case HANDSHAKING:
|
||||
handshake(ctx, buf);
|
||||
break;
|
||||
case CONNECTING:
|
||||
connecting(ctx, buf);
|
||||
break;
|
||||
case CONNECTED:
|
||||
connected(ctx, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handshake(ChannelHandlerContext ctx, ByteBuf buf)
|
||||
{
|
||||
byte type = buf.readByte();
|
||||
if (type != 15)
|
||||
{
|
||||
logger.warn("Expected handshake type 15, got {}", type);
|
||||
ctx.close();
|
||||
return;
|
||||
}
|
||||
|
||||
int revision = buf.readInt();
|
||||
if (revision != server.getRevision())
|
||||
{
|
||||
logger.warn("Incorrect version for client {}, expected {}", revision, server.getRevision());
|
||||
ctx.close();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Handshake complete from client {}, type {}, revision {}", ctx.channel().remoteAddress(), type, revision);
|
||||
|
||||
// Write response
|
||||
ByteBuf buffer = Unpooled.buffer(1);
|
||||
buffer.writeByte(HelloHandshake.RESPONSE_OK);
|
||||
ctx.writeAndFlush(buffer);
|
||||
|
||||
state = ClientState.CONNECTING;
|
||||
}
|
||||
|
||||
private void connecting(ChannelHandlerContext ctx, ByteBuf buf)
|
||||
{
|
||||
ConnectionInfo cinfo = new ConnectionInfo();
|
||||
cinfo.setType(buf.readByte());
|
||||
cinfo.setPadding(buf.readMedium());
|
||||
|
||||
logger.info("Connect info from client {} - type {}", ctx.channel().remoteAddress(), cinfo.getType());
|
||||
|
||||
state = ClientState.CONNECTED;
|
||||
}
|
||||
|
||||
private void connected(ChannelHandlerContext ctx, ByteBuf buf)
|
||||
{
|
||||
// a request for an archive
|
||||
// byte[0] = 1 if requesting index 255, else 0
|
||||
// byte[1] = index
|
||||
// byte[2-3] = archive id
|
||||
|
||||
byte requesting255 = buf.readByte();
|
||||
int index = buf.readByte() & 0xFF;
|
||||
int archiveId = buf.readShort() & 0xFFFF;
|
||||
|
||||
if (requesting255 != 0)
|
||||
{
|
||||
handle255(ctx, index, archiveId);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleRequest(ctx, index, archiveId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle255(ChannelHandlerContext ctx, int index, int archiveId)
|
||||
{
|
||||
logger.info("Client {} requests 255, index {}, archive {}", ctx.channel().remoteAddress(), index, archiveId);
|
||||
|
||||
if (archiveId == 255)
|
||||
{
|
||||
// index 255 data, for each index:
|
||||
// 4 byte crc
|
||||
// 4 byte revision
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
for (Index i : store.getIndexes())
|
||||
{
|
||||
buffer.writeInt(i.getCrc());
|
||||
buffer.writeInt(i.getRevision());
|
||||
}
|
||||
|
||||
byte[] compressed = compress(CompressionType.NONE, Arrays.copyOf(buffer.array(), buffer.readableBytes()));
|
||||
byte[] packed = addHeader(index, archiveId, compressed);
|
||||
byte[] chunked = chunk(packed);
|
||||
|
||||
ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked));
|
||||
}
|
||||
else
|
||||
{
|
||||
Index i = store.findIndex(archiveId);
|
||||
assert i != null;
|
||||
|
||||
byte[] indexData = i.writeIndexData();
|
||||
|
||||
byte[] compressed = compress(CompressionType.NONE, indexData);
|
||||
byte[] packed = addHeader(255, archiveId, compressed);
|
||||
byte[] chunked = chunk(packed);
|
||||
|
||||
ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRequest(ChannelHandlerContext ctx, int index, int archiveId)
|
||||
{
|
||||
logger.info("Client {} requests index {} archive {}", ctx.channel().remoteAddress(), index, archiveId);
|
||||
|
||||
Index i = store.findIndex(index);
|
||||
assert i != null;
|
||||
|
||||
Archive archive = i.getArchive(archiveId);
|
||||
assert archive != null;
|
||||
|
||||
byte[] packed;
|
||||
if (archive.getData() != null)
|
||||
{
|
||||
byte[] data = archive.getData(); // is compressed, includes length and type
|
||||
packed = addHeader(index, archiveId, Arrays.copyOf(data, data.length - 2)); // compressed size includes revision..
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] data = archive.saveContents();
|
||||
data = compress(archive.getCompression(), data);
|
||||
|
||||
packed = addHeader(index, archiveId, data);
|
||||
}
|
||||
|
||||
byte[] chunked = chunk(packed);
|
||||
|
||||
ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked));
|
||||
}
|
||||
|
||||
private byte[] compress(int compression, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DataFile.compress(data, compression, -1, null);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add header to archive
|
||||
*
|
||||
* @param index
|
||||
* @param archive
|
||||
* @param file compressed archive
|
||||
* @return
|
||||
*/
|
||||
private byte[] addHeader(int index, int archive, byte[] file)
|
||||
{
|
||||
// archive file header
|
||||
// 1 byte index
|
||||
// 2 byte archive
|
||||
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
buf.writeByte((byte) index);
|
||||
buf.writeShort((short) archive);
|
||||
buf.writeBytes(file);
|
||||
|
||||
return Arrays.copyOf(buf.array(), buf.readableBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert in chunk markers to data
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
*/
|
||||
private byte[] chunk(byte[] in)
|
||||
{
|
||||
Chunker chunker = new Chunker(in);
|
||||
return chunker.chunkData();
|
||||
}
|
||||
|
||||
}
|
||||
78
cache/src/main/java/net/runelite/cache/server/Chunker.java
vendored
Normal file
78
cache/src/main/java/net/runelite/cache/server/Chunker.java
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.cache.server;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.Arrays;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Chunker
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Chunker.class);
|
||||
|
||||
private static final int CHUNK_SIZE = 512;
|
||||
|
||||
private final byte[] data;
|
||||
|
||||
public Chunker(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split data into 512 byte chunks, with the first byte of each chunk
|
||||
* being 0xff, except for the first chunk.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public byte[] chunkData()
|
||||
{
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
|
||||
int pos = 0;
|
||||
int remaining = data.length;
|
||||
|
||||
int put = Math.min(CHUNK_SIZE, remaining);
|
||||
buf.writeBytes(data, pos, put);
|
||||
|
||||
pos += put;
|
||||
remaining -= put;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
buf.writeByte((byte) 0xff);
|
||||
|
||||
put = Math.min(CHUNK_SIZE - 1, remaining);
|
||||
buf.writeBytes(data, pos, put);
|
||||
|
||||
pos += put;
|
||||
remaining -= put;
|
||||
}
|
||||
|
||||
return Arrays.copyOf(buf.array(), buf.readableBytes());
|
||||
}
|
||||
}
|
||||
32
cache/src/main/java/net/runelite/cache/server/ClientState.java
vendored
Normal file
32
cache/src/main/java/net/runelite/cache/server/ClientState.java
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.cache.server;
|
||||
|
||||
public enum ClientState
|
||||
{
|
||||
HANDSHAKING,
|
||||
CONNECTING,
|
||||
CONNECTED
|
||||
}
|
||||
@@ -67,4 +67,27 @@ public class CacheClientTest
|
||||
|
||||
store.save();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testTree() throws Exception
|
||||
{
|
||||
Store store = new Store(new File("C:\\rs\\temp"));
|
||||
store.loadTree(new File("C:\\rs\\runescape-data\\cache"));
|
||||
|
||||
CacheClient c = new CacheClient(store);
|
||||
c.connect();
|
||||
CompletableFuture<Integer> handshake = c.handshake();
|
||||
|
||||
Integer result = handshake.get();
|
||||
logger.info("Handshake result: {}", result);
|
||||
|
||||
Assert.assertEquals(0, (int) result);
|
||||
|
||||
c.download();
|
||||
|
||||
c.close();
|
||||
|
||||
store.saveTree(new File("C:\\rs\\temp\\t"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,12 @@ public class StoreLoadTest
|
||||
@Test
|
||||
public void testLoad() throws IOException
|
||||
{
|
||||
Store store = new Store(StoreLocation.LOCATION);
|
||||
store.load();
|
||||
System.out.println(store);
|
||||
try (Store store = new Store(StoreLocation.LOCATION))
|
||||
{
|
||||
store.load();
|
||||
|
||||
System.out.println(store);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
104
cache/src/test/java/net/runelite/cache/server/CacheServerTest.java
vendored
Normal file
104
cache/src/test/java/net/runelite/cache/server/CacheServerTest.java
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Adam <Adam@sigterm.info>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.cache.server;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import net.runelite.cache.StoreLocation;
|
||||
import net.runelite.cache.downloader.CacheClient;
|
||||
import net.runelite.cache.fs.Archive;
|
||||
import net.runelite.cache.fs.File;
|
||||
import net.runelite.cache.fs.Index;
|
||||
import net.runelite.cache.fs.Store;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
public class CacheServerTest
|
||||
{
|
||||
private static final String HOST = "localhost";
|
||||
private static final int REVISION = 139;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = StoreLocation.getTemporaryFolder();
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testDownload() throws Exception
|
||||
{
|
||||
try (Store store = new Store(StoreLocation.LOCATION);
|
||||
CacheServer server = new CacheServer(store, REVISION))
|
||||
{
|
||||
store.load();
|
||||
store.rebuildCrc();
|
||||
|
||||
server.start();
|
||||
|
||||
try (CacheClient client = new CacheClient(new Store(folder.newFolder()), HOST, REVISION))
|
||||
{
|
||||
client.connect();
|
||||
client.handshake().get();
|
||||
client.download();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServer() throws Exception
|
||||
{
|
||||
try (Store store = new Store(folder.newFolder());
|
||||
CacheServer server = new CacheServer(store, REVISION))
|
||||
{
|
||||
addInitialFilesToStore(store);
|
||||
|
||||
store.rebuildCrc();
|
||||
|
||||
server.start();
|
||||
|
||||
try (Store store2 = new Store(folder.newFolder()); CacheClient client = new CacheClient(store2, HOST, REVISION))
|
||||
{
|
||||
client.connect();
|
||||
client.handshake().get();
|
||||
client.download();
|
||||
|
||||
Index index = store2.findIndex(0);
|
||||
Archive archive = index.getArchive(0);
|
||||
File file = archive.getFiles().get(0);
|
||||
Assert.assertArrayEquals("test".getBytes(), file.getContents());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addInitialFilesToStore(Store store) throws FileNotFoundException
|
||||
{
|
||||
Index index = store.addIndex(0);
|
||||
Archive archive = index.addArchive(0);
|
||||
File file = archive.addFile(0);
|
||||
file.setNameHash(7);
|
||||
file.setContents("test".getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user