From 220175b80bd44693363db884456e1c85722551c3 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 25 Mar 2017 18:31:15 -0400 Subject: [PATCH] cache: add cache server --- .../cache/downloader/CacheClient.java | 17 +- .../cache/downloader/CacheClientHandler.java | 6 + .../java/net/runelite/cache/fs/Archive.java | 2 +- .../java/net/runelite/cache/fs/DataFile.java | 2 +- .../java/net/runelite/cache/fs/Index.java | 51 ++++ .../java/net/runelite/cache/fs/Store.java | 10 + .../runelite/cache/server/CacheServer.java | 104 ++++++++ .../cache/server/CacheServerHandler.java | 250 ++++++++++++++++++ .../net/runelite/cache/server/Chunker.java | 78 ++++++ .../runelite/cache/server/ClientState.java | 32 +++ .../cache/downloader/CacheClientTest.java | 23 ++ .../net/runelite/cache/fs/StoreLoadTest.java | 9 +- .../cache/server/CacheServerTest.java | 104 ++++++++ 13 files changed, 676 insertions(+), 12 deletions(-) create mode 100644 cache/src/main/java/net/runelite/cache/server/CacheServer.java create mode 100644 cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java create mode 100644 cache/src/main/java/net/runelite/cache/server/Chunker.java create mode 100644 cache/src/main/java/net/runelite/cache/server/ClientState.java create mode 100644 cache/src/test/java/net/runelite/cache/server/CacheServerTest.java diff --git a/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java b/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java index 3d4b0826a8..cae0b8b157 100644 --- a/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java +++ b/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java @@ -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 handshakeFuture; - private Queue requests = new ArrayDeque<>(); + private final Queue 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(); diff --git a/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java b/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java index 252fe8a4df..5ddc62df3d 100644 --- a/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java +++ b/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java @@ -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(); diff --git a/cache/src/main/java/net/runelite/cache/fs/Archive.java b/cache/src/main/java/net/runelite/cache/fs/Archive.java index 57c016bc22..bf5a985832 100644 --- a/cache/src/main/java/net/runelite/cache/fs/Archive.java +++ b/cache/src/main/java/net/runelite/cache/fs/Archive.java @@ -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)) diff --git a/cache/src/main/java/net/runelite/cache/fs/DataFile.java b/cache/src/main/java/net/runelite/cache/fs/DataFile.java index 8e3ed1efb4..5a70379782 100644 --- a/cache/src/main/java/net/runelite/cache/fs/DataFile.java +++ b/cache/src/main/java/net/runelite/cache/fs/DataFile.java @@ -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; diff --git a/cache/src/main/java/net/runelite/cache/fs/Index.java b/cache/src/main/java/net/runelite/cache/fs/Index.java index b0aee52de8..120c3a4ce8 100644 --- a/cache/src/main/java/net/runelite/cache/fs/Index.java +++ b/cache/src/main/java/net/runelite/cache/fs/Index.java @@ -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(); diff --git a/cache/src/main/java/net/runelite/cache/fs/Store.java b/cache/src/main/java/net/runelite/cache/fs/Store.java index 1976856117..2270386c38 100644 --- a/cache/src/main/java/net/runelite/cache/fs/Store.java +++ b/cache/src/main/java/net/runelite/cache/fs/Store.java @@ -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) diff --git a/cache/src/main/java/net/runelite/cache/server/CacheServer.java b/cache/src/main/java/net/runelite/cache/server/CacheServer.java new file mode 100644 index 0000000000..ab0e816cdf --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/CacheServer.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, Adam + * 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() + { + @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; + } +} diff --git a/cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java b/cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java new file mode 100644 index 0000000000..41e0e6f92e --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2017, Adam + * 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 +{ + 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(); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/server/Chunker.java b/cache/src/main/java/net/runelite/cache/server/Chunker.java new file mode 100644 index 0000000000..fb18f350a8 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/Chunker.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017, Adam + * 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()); + } +} diff --git a/cache/src/main/java/net/runelite/cache/server/ClientState.java b/cache/src/main/java/net/runelite/cache/server/ClientState.java new file mode 100644 index 0000000000..fc04db3ea1 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/ClientState.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017, Adam + * 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 +} diff --git a/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java b/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java index ab38a20fce..699041367d 100644 --- a/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java +++ b/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java @@ -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 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")); + } } diff --git a/cache/src/test/java/net/runelite/cache/fs/StoreLoadTest.java b/cache/src/test/java/net/runelite/cache/fs/StoreLoadTest.java index c8052e9975..1e9a3d06c3 100644 --- a/cache/src/test/java/net/runelite/cache/fs/StoreLoadTest.java +++ b/cache/src/test/java/net/runelite/cache/fs/StoreLoadTest.java @@ -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 diff --git a/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java b/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java new file mode 100644 index 0000000000..1177d28fc9 --- /dev/null +++ b/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, Adam + * 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()); + } + +}