* Got the basics working

This commit is contained in:
Nicolas BARBOTIN 2018-02-07 21:40:54 +01:00
parent 6c07fd17d4
commit 98ae84bf58
21 changed files with 883 additions and 93 deletions

View File

@ -88,4 +88,10 @@ public class SharedProxy {
return FMLServerHandler.instance().getServer();
}
public void setMiniservClientPort(int port) {
}
public void startMiniServClient() {
}
}

View File

@ -22,9 +22,7 @@ import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.*;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
@ -36,6 +34,9 @@ import net.montoyo.wd.block.BlockScreen;
import net.montoyo.wd.core.*;
import net.montoyo.wd.entity.TileEntityScreen;
import net.montoyo.wd.item.*;
import net.montoyo.wd.miniserv.client.Client;
import net.montoyo.wd.miniserv.server.Server;
import net.montoyo.wd.net.CMessageServerInfo;
import net.montoyo.wd.net.Messages;
import net.montoyo.wd.utilities.Log;
import net.montoyo.wd.utilities.Util;
@ -183,7 +184,8 @@ public class WebDisplays {
if(ev.getWorld().isRemote || ev.getWorld().provider.getDimension() != 0)
return;
File f = new File(ev.getWorld().getSaveHandler().getWorldDirectory(), "wd_next.txt");
File worldDir = ev.getWorld().getSaveHandler().getWorldDirectory();
File f = new File(worldDir, "wd_next.txt");
if(f.exists()) {
try {
@ -203,6 +205,10 @@ public class WebDisplays {
Log.warningEx("Could not read last minePad ID from %s. I'm afraid this might break all minePads.", t, f.getAbsolutePath());
}
}
Server sv = Server.getInstance();
sv.setDirectory(new File(worldDir, "wd_filehost"));
sv.start(); //TODO: Configure port
}
@SubscribeEvent
@ -254,6 +260,23 @@ public class WebDisplays {
}
}
@Mod.EventHandler
public void onServerStop(FMLServerStoppingEvent ev) {
Server.getInstance().stopServer();
}
@SubscribeEvent
public void onLogIn(PlayerEvent.PlayerLoggedInEvent ev) {
if(!ev.player.world.isRemote && ev.player instanceof EntityPlayerMP)
WebDisplays.NET_HANDLER.sendTo(new CMessageServerInfo(25566), (EntityPlayerMP) ev.player); //TODO: Port config
}
@SubscribeEvent
public void onLogOut(PlayerEvent.PlayerLoggedOutEvent ev) {
if(!ev.player.world.isRemote)
Server.getInstance().getClientManager().revokeClientKey(ev.player.getGameProfile().getId());
}
private boolean hasPlayerAdvancement(EntityPlayerMP ply, ResourceLocation rl) {
MinecraftServer server = PROXY.getServer();
if(server == null)

View File

@ -52,12 +52,15 @@ import net.montoyo.wd.core.JSServerRequest;
import net.montoyo.wd.data.GuiData;
import net.montoyo.wd.entity.TileEntityScreen;
import net.montoyo.wd.item.ItemMulti;
import net.montoyo.wd.miniserv.client.Client;
import net.montoyo.wd.net.SMessagePadCtrl;
import net.montoyo.wd.net.SMessageScreenCtrl;
import net.montoyo.wd.utilities.*;
import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.*;
public class ClientProxy extends SharedProxy implements IResourceManagerReloadListener, IDisplayHandler, IJSQueryHandler {
@ -84,6 +87,8 @@ public class ClientProxy extends SharedProxy implements IResourceManagerReloadLi
private MinePadRenderer minePadRenderer;
private JSQueryDispatcher jsDispatcher;
private LaserPointerRenderer laserPointerRenderer;
private int miniservPort;
private boolean msClientStarted;
//Client-side advancement hack
private final Field advancementToProgressField = findAdvancementToProgressField();
@ -295,6 +300,34 @@ public class ClientProxy extends SharedProxy implements IResourceManagerReloadLi
q.error(errCode, err);
}
@Override
public void setMiniservClientPort(int port) {
miniservPort = port;
}
@Override
public void startMiniServClient() {
if(miniservPort <= 0) {
Log.warning("Can't start miniserv client: miniserv is disabled");
return;
}
if(mc.player == null) {
Log.warning("Can't start miniserv client: player is null");
return;
}
SocketAddress saddr = mc.player.connection.getNetworkManager().channel().remoteAddress();
if(saddr == null || !(saddr instanceof InetSocketAddress)) {
Log.warning("Miniserv client: remote address is not inet, assuming local address");
saddr = new InetSocketAddress("127.0.0.1", 1234);
}
InetSocketAddress msAddr = new InetSocketAddress(((InetSocketAddress) saddr).getAddress(), miniservPort);
Client.getInstance().start(msAddr);
msClientStarted = true;
}
/**************************************** RESOURCE MANAGER METHODS ****************************************/
@Override
@ -507,6 +540,12 @@ public class ClientProxy extends SharedProxy implements IResourceManagerReloadLi
//Handle JS queries
jsDispatcher.handleQueries();
//Stop miniserv client
if(mc.player == null && msClientStarted) {
msClientStarted = false;
Client.getInstance().stop();
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client;
import net.montoyo.mcef.api.IScheme;
import net.montoyo.mcef.api.ISchemeResponseData;
import net.montoyo.mcef.api.ISchemeResponseHeaders;
import net.montoyo.mcef.api.SchemePreResponse;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.miniserv.Constants;
import net.montoyo.wd.miniserv.client.Client;
import net.montoyo.wd.miniserv.client.ClientTaskGetFile;
import net.montoyo.wd.utilities.Util;
import java.util.UUID;
public class WDScheme implements IScheme {
private ClientTaskGetFile task;
@Override
public SchemePreResponse processRequest(String url) {
url = url.substring("wd://".length());
int pos = url.indexOf('/');
if(pos < 0)
return SchemePreResponse.NOT_HANDLED;
String uuidStr = url.substring(0, pos);
String fileStr = url.substring(pos + 1);
if(uuidStr.isEmpty() || Util.isFileNameInvalid(fileStr))
return SchemePreResponse.NOT_HANDLED;
UUID uuid;
try {
uuid = UUID.fromString(uuidStr);
} catch(IllegalArgumentException ex) {
return SchemePreResponse.NOT_HANDLED; //Invalid UUID
}
task = new ClientTaskGetFile(uuid, fileStr);
return Client.getInstance().addTask(task) ? SchemePreResponse.HANDLED_CONTINUE : SchemePreResponse.NOT_HANDLED;
}
@Override
public void getResponseHeaders(ISchemeResponseHeaders resp) {
int status = task.waitForResponse();
if(status == 0) {
//OK
int extPos = task.getFileName().lastIndexOf('.');
if(extPos >= 0) {
String mime = ((ClientProxy) WebDisplays.PROXY).getMCEF().mimeTypeFromExtension(task.getFileName().substring(extPos + 1));
if(mime != null)
resp.setMimeType(mime);
}
resp.setStatus(200);
resp.setStatusText("OK");
resp.setResponseLength(-1);
} else if(status == Constants.GETF_STATUS_NOT_FOUND) {
resp.setStatus(404);
resp.setStatusText("Not Found");
resp.setResponseLength(0);
} else {
resp.setStatus(500);
resp.setStatusText("Internal Server Error");
resp.setResponseLength(0);
}
}
private byte[] dataToWrite;
private int dataOffset;
private int amountToWrite;
@Override
public boolean readResponse(ISchemeResponseData data) {
if(dataToWrite == null) {
dataToWrite = task.waitForData();
dataOffset = 3; //packet ID + size
amountToWrite = task.getDataLength();
if(amountToWrite <= 0) {
dataToWrite = null;
data.setAmountRead(0);
return false;
}
}
int toWrite = data.getBytesToRead();
if(toWrite > amountToWrite)
toWrite = amountToWrite;
System.arraycopy(dataToWrite, dataOffset, data.getDataArray(), 0, toWrite);
data.setAmountRead(toWrite);
dataOffset += toWrite;
amountToWrite -= toWrite;
if(amountToWrite <= 0) {
task.nextData();
dataToWrite = null;
}
return true;
}
}

View File

@ -28,7 +28,7 @@ public abstract class AbstractClient {
private final Method[] packetHandlers = new Method[PacketID.values().length];
protected SocketChannel socket;
protected Selector selector;
private SelectionKey writeKey;
protected SelectionKey selKey;
public AbstractClient() {
sendBuffer.limit(0);
@ -67,13 +67,17 @@ public abstract class AbstractClient {
if(pid >= packetHandlers.length)
Log.error("Caught invalid packet ID %d", pid);
else if(packetHandlers[pid] != null) {
try {
packetHandlers[pid].invoke(this, dis); //This is slow, I know... sorry
} catch(IllegalAccessException ex) {
Log.errorEx("This shouldn't have happened", ex);
} catch(InvocationTargetException ex) {
Log.warningEx("Caught exception while handling packet %d", ex.getTargetException(), pid);
else {
Log.info("Received PID %s", PacketID.fromInt(pid).toString());
if(packetHandlers[pid] != null) {
try {
packetHandlers[pid].invoke(this, dis); //This is slow, I know... sorry
} catch(IllegalAccessException ex) {
Log.errorEx("This shouldn't have happened", ex);
} catch(InvocationTargetException ex) {
Log.warningEx("Caught exception while handling packet %d", ex.getTargetException(), pid);
}
}
}
} catch(IOException ex) {
@ -88,8 +92,13 @@ public abstract class AbstractClient {
public void readyWrite() throws Throwable {
if(sendBuffer.remaining() > 0 || fillSendBuffer()) {
if(socket.write(sendBuffer) < 0)
int sent = socket.write(sendBuffer);
Log.info("Sent %d bytes", sent);
if(sent < 0) {
Log.error("Error when sending data");
onWriteError();
}
}
}
@ -103,12 +112,8 @@ public abstract class AbstractClient {
pkt = sendQueue.poll();
if(pkt == null) {
if(writeKey != null) {
writeKey.cancel();
writeKey = null;
}
return sendBuffer.remaining() > 0;
selKey.interestOps(SelectionKey.OP_READ); //Remove write op
break;
}
}
@ -116,19 +121,19 @@ public abstract class AbstractClient {
}
} while(sendBuffer.remaining() > 0);
return true;
int pos = sendBuffer.position();
sendBuffer.position(0);
sendBuffer.limit(pos);
return pos > 0;
}
public void sendPacket(OutgoingPacket pkt) {
synchronized(sendQueue) {
sendQueue.offer(pkt);
if(writeKey == null) {
try {
writeKey = socket.register(selector, SelectionKey.OP_WRITE);
} catch(ClosedChannelException ex) {
Log.warningEx("Couldn't send packet", ex);
}
if((selKey.interestOps() & SelectionKey.OP_WRITE) == 0) {
selKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
selector.wakeup(); //Is this needed?
}
}
}
@ -149,4 +154,11 @@ public abstract class AbstractClient {
return packetReader.getPacketData();
}
protected void clearSendQueue() {
synchronized(sendQueue) {
packetWriter.clear();
sendQueue.clear();
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.miniserv;
public abstract class Constants {
public static int FUPA_STATUS_BAD_NAME = 1;
public static int FUPA_STATUS_INVALID_SIZE = 2;
public static int FUPA_STATUS_EXCEEDS_QUOTA = 3;
public static int FUPA_STATUS_OCCUPIED = 4;
public static int FUPA_STATUS_FILE_EXISTS = 5;
public static int FUPA_STATUS_INTERNAL_ERROR = 6;
public static int FUPA_STATUS_USER_ABORT = 7;
public static int FUPA_STATUS_LIER = 8;
public static int FUPA_STATUS_CONNECTION_LOST = 9;
public static int GETF_STATUS_BAD_NAME = 1;
public static int GETF_STATUS_NOT_FOUND = 2;
public static int GETF_STATUS_INTERNAL_ERROR = 3;
public static int GETF_STATUS_CONNECTION_LOST = 4;
}

View File

@ -18,6 +18,12 @@ public final class OutgoingPacket {
dos = new DataOutputStream(baos);
}
public final void writeLong(long l) {
try {
dos.writeLong(l);
} catch(IOException ex) {}
}
public final void writeInt(int i) {
try {
dos.writeInt(i);

View File

@ -28,12 +28,16 @@ public final class PacketReader {
return true; //Abort packet reading
}
packetSize -= 4;
packetData = new byte[packetSize];
Log.info("Awaiting packet of size %d", packetSize);
} else
return false;
}
return readByteArray(packetData, buf);
boolean ret = readByteArray(packetData, buf);
Log.info("Read %d out of %d, ok = %s", pos, packetData.length, ret ? "true" : "false");
return ret;
}
private boolean readByteArray(byte[] dst, ByteBuffer src) {

View File

@ -53,4 +53,10 @@ public final class PacketWriter {
needToWriteSize = true;
}
public final void clear() {
packet = null;
pos = 0;
needToWriteSize = true;
}
}

View File

@ -4,6 +4,7 @@
package net.montoyo.wd.miniserv.client;
import net.minecraft.client.Minecraft;
import net.montoyo.wd.miniserv.*;
import net.montoyo.wd.net.SMessageMiniservConnect;
import net.montoyo.wd.utilities.Log;
@ -20,6 +21,8 @@ import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayDeque;
import java.util.UUID;
public class Client extends AbstractClient implements Runnable {
@ -36,10 +39,14 @@ public class Client extends AbstractClient implements Runnable {
private KeyPair keyPair;
private byte[] key;
private SocketAddress address;
private boolean running = true;
private volatile boolean running;
private volatile boolean connected;
private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(8192);
private final Thread thread = new Thread(this);
private boolean authenticated = false;
private volatile Thread thread;
private final UUID clientUUID = Minecraft.getMinecraft().player.getGameProfile().getId();
private final ArrayDeque<ClientTask> tasks = new ArrayDeque<>();
private ClientTask currentTask;
private volatile boolean authenticated;
public SMessageMiniservConnect beginConnection() {
if(keyPair == null) {
@ -74,7 +81,7 @@ public class Client extends AbstractClient implements Runnable {
return false;
}
public byte[] authenticate(byte[] challenge) {
private byte[] authenticate(byte[] challenge) {
try {
Mac mac = Mac.getInstance(KeyParameters.MAC_ALGORITHM);
mac.init(new SecretKeySpec(key, KeyParameters.MAC_ALGORITHM));
@ -89,25 +96,85 @@ public class Client extends AbstractClient implements Runnable {
}
public void start(SocketAddress addr) {
if(getRunning()) {
Log.warning("Called Client.start() twice");
return;
}
address = addr;
thread = new Thread(this);
thread.setName("MiniServClient");
thread.setDaemon(true);
synchronized(this) {
running = true;
connected = false;
}
thread.start();
}
public void stop() {
if(getRunning()) {
Thread thread = this.thread;
synchronized(this) {
running = false;
if(connected)
selector.wakeup();
}
while(thread.isAlive()) {
try {
thread.join();
} catch(InterruptedException ex) { }
}
Log.info("Miniserv client stopped");
}
}
private boolean getRunning() {
boolean ret;
synchronized(this) {
ret = running;
}
return ret;
}
@Override
public void run() {
try {
selector = Selector.open();
socket = SocketChannel.open();
socket.connect(address);
socket.configureBlocking(false);
selector = Selector.open();
socket.register(selector, SelectionKey.OP_READ);
selKey = socket.register(selector, SelectionKey.OP_READ);
} catch(IOException ex) {
Log.errorEx("Couldn't start client", ex);
synchronized(this) {
running = false;
}
return;
}
while(running) {
synchronized(this) {
connected = true;
}
Log.info("Miniserv client connected!");
OutgoingPacket connPacket = new OutgoingPacket();
connPacket.writeByte(PacketID.INIT_CONN.ordinal());
connPacket.writeLong(clientUUID.getMostSignificantBits());
connPacket.writeLong(clientUUID.getLeastSignificantBits());
sendPacket(connPacket);
while(getRunning()) {
try {
unsafeLoop();
} catch(Throwable t) {
@ -116,22 +183,51 @@ public class Client extends AbstractClient implements Runnable {
}
}
Util.silentClose(socket);
synchronized(this) {
connected = false;
running = false;
authenticated = false;
}
Util.silentClose(selector);
Util.silentClose(socket);
selector = null;
socket = null;
if(currentTask != null) {
currentTask.abort();
currentTask.onFinished();
currentTask = null;
}
synchronized(tasks) {
ClientTask task;
while((task = tasks.poll()) != null) {
task.abort();
task.onFinished();
}
}
clearSendQueue();
thread = null;
}
private void unsafeLoop() throws Throwable {
selector.select();
if(currentTask == null)
nextTask();
for(SelectionKey key: selector.selectedKeys()) {
if(key.isReadable()) {
readBuffer.clear();
int rd = socket.read(readBuffer);
if(rd <= 0) {
if(rd < 0) {
Log.warning("Connection was closed, stopping...");
running = false;
} else {
} else if(rd > 0) {
readBuffer.position(0);
readBuffer.limit(rd);
readyRead(readBuffer);
@ -156,8 +252,78 @@ public class Client extends AbstractClient implements Runnable {
}
@PacketHandler(PacketID.AUTHENTICATE)
public void handleAuth(DataInputStream dis) {
//TODO: Do some stuff
public void handleAuth(DataInputStream dis) throws IOException {
int len = dis.readByte();
byte[] challenge = new byte[len];
dis.readFully(challenge);
byte[] mac = authenticate(challenge);
OutgoingPacket pkt = new OutgoingPacket();
pkt.writeByte(PacketID.AUTHENTICATE.ordinal());
pkt.writeByte(mac.length);
pkt.writeBytes(mac);
sendPacket(pkt);
Log.info("Miniserv client authenticated");
synchronized(this) {
authenticated = true;
}
}
@PacketHandler(PacketID.BEGIN_FILE_UPLOAD)
public void handleBeginUpload(DataInputStream dis) throws IOException {
if(currentTask instanceof ClientTaskUploadFile)
((ClientTaskUploadFile) currentTask).onReceivedUploadStatus(dis.readByte());
}
@PacketHandler(PacketID.FILE_STATUS)
public void handleFileStatus(DataInputStream dis) throws IOException {
if(currentTask instanceof ClientTaskUploadFile)
((ClientTaskUploadFile) currentTask).onUploadFinishedStatus(dis.readByte());
}
@PacketHandler(PacketID.GET_FILE)
public void handleGetFile(DataInputStream dis) throws IOException {
if(currentTask instanceof ClientTaskGetFile)
((ClientTaskGetFile) currentTask).onGetFileResponse(dis.readByte());
}
@PacketHandler(PacketID.FILE_PART)
public void handleFilePart(DataInputStream dis) throws IOException {
if(currentTask instanceof ClientTaskGetFile) {
int len = dis.readShort() & 0xFFFF;
((ClientTaskGetFile) currentTask).onData(getCurrentPacketRawData(), len);
}
}
public void nextTask() {
if(currentTask != null)
currentTask.onFinished();
synchronized(tasks) {
currentTask = tasks.poll();
}
if(currentTask != null)
currentTask.start();
}
public boolean addTask(ClientTask task) {
boolean cancel;
synchronized(this) {
cancel = !running || !authenticated;
}
if(cancel)
return false;
synchronized(tasks) {
tasks.offer(task);
}
selector.wakeup();
return true;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.miniserv.client;
import java.util.function.Consumer;
public abstract class ClientTask {
private Consumer<ClientTask> finishCallback;
protected final Client client = Client.getInstance();
public abstract void start();
public abstract void abort();
public void onFinished() {
//Called by Client, don't call it from a ClientTask!
if(finishCallback != null)
finishCallback.accept(this);
}
public void setFinishCallback(Consumer<ClientTask> finishCallback) {
this.finishCallback = finishCallback;
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.miniserv.client;
import net.montoyo.wd.miniserv.Constants;
import net.montoyo.wd.miniserv.OutgoingPacket;
import net.montoyo.wd.miniserv.PacketID;
import java.util.UUID;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ClientTaskGetFile extends ClientTask {
private final UUID uuid;
private final String fname;
private int response;
private boolean hasResponse;
private final ReentrantLock responseLock = new ReentrantLock();
private final Condition gotResponse = responseLock.newCondition();
private final ReentrantLock dataLock = new ReentrantLock();
private final Condition dataChanged = dataLock.newCondition();
private byte[] data;
private int dataLen;
public ClientTaskGetFile(UUID id, String name) {
uuid = id;
fname = name;
}
@Override
public void start() {
OutgoingPacket pkt = new OutgoingPacket();
pkt.writeByte(PacketID.GET_FILE.ordinal());
pkt.writeLong(uuid.getMostSignificantBits());
pkt.writeLong(uuid.getLeastSignificantBits());
pkt.writeString(fname);
client.sendPacket(pkt);
}
@Override
public void abort() {
responseLock.lock();
if(!hasResponse) {
response = Constants.GETF_STATUS_CONNECTION_LOST;
hasResponse = true;
gotResponse.signal();
}
responseLock.unlock();
onData(new byte[0], -1); //This will trigger an error
}
public void onGetFileResponse(int status) {
boolean triggerError = false;
responseLock.lock();
if(hasResponse) {
if(status != 0)
triggerError = true;
} else {
response = status;
hasResponse = true;
gotResponse.signal();
}
responseLock.unlock();
if(triggerError)
onData(new byte[0], -1);
}
public int waitForResponse() {
responseLock.lock();
while(!hasResponse) {
try {
gotResponse.await();
} catch(InterruptedException ex) {}
}
responseLock.unlock();
return response;
}
public void onData(byte[] data, int len) {
dataLock.lock();
while(this.data != null) {
try {
dataChanged.await();
} catch(InterruptedException ex) {}
}
this.data = data;
dataLen = len;
dataChanged.signal();
dataLock.unlock();
if(len <= 0)
client.nextTask();
}
public byte[] waitForData() {
dataLock.lock();
while(this.data == null) {
try {
dataChanged.await();
} catch(InterruptedException ex) {}
}
dataLock.unlock(); //This won't change until data is null again
return data;
}
public int getDataLength() {
return dataLen;
}
public void nextData() {
dataLock.lock();
data = null;
dataChanged.signal();
dataLock.unlock();
}
public String getFileName() {
return fname;
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.miniserv.client;
import net.montoyo.wd.miniserv.Constants;
import net.montoyo.wd.miniserv.OutgoingPacket;
import net.montoyo.wd.miniserv.PacketID;
import net.montoyo.wd.utilities.Log;
import net.montoyo.wd.utilities.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.function.Consumer;
public class ClientTaskUploadFile extends ClientTask implements Consumer<OutgoingPacket> {
private static final byte[] UPLOAD_BUFFER = new byte[65536];
private final File file;
private final long size;
private FileInputStream fis;
private boolean abortFupa;
private int uploadStatus;
public ClientTaskUploadFile(File fle) throws IOException {
file = fle;
size = Files.size(fle.toPath());
fis = new FileInputStream(fle);
}
@Override
public void start() {
OutgoingPacket pkt = new OutgoingPacket();
pkt.writeByte(PacketID.BEGIN_FILE_UPLOAD.ordinal());
pkt.writeString(file.getName());
pkt.writeLong(size);
client.sendPacket(pkt);
}
@Override
public void abort() {
abortFupa = true;
setUploadStatus(Constants.FUPA_STATUS_CONNECTION_LOST);
Util.silentClose(fis);
}
public void onReceivedUploadStatus(int status) {
if(status == 0) {
//Begin upload
Log.info("Now uploading %s", file.getName());
accept(null);
} else {
Util.silentClose(fis);
setUploadStatus(status);
client.nextTask();
}
}
public void onUploadFinishedStatus(int status) {
abortFupa = true; //This isn't necessary, but just in case...
setUploadStatus(status);
client.nextTask();
}
@Override
public void accept(OutgoingPacket nocare) {
if(abortFupa)
return;
int rd;
do {
try {
rd = fis.read(UPLOAD_BUFFER);
} catch(IOException ex) {
Log.warningEx("Caught IOException while sending some file", ex);
rd = 0; //This will cause a FUPA_STATUS_USER_ABORT
break;
}
} while(rd == 0);
if(rd >= 0) { //If rd < 0, end of file, we're done.
OutgoingPacket pkt = new OutgoingPacket();
pkt.writeByte(PacketID.FILE_PART.ordinal());
pkt.writeShort(rd);
pkt.writeBytes(UPLOAD_BUFFER, 0, rd);
client.sendPacket(pkt);
if(rd > 0) {
pkt.setOnFinishAction(this);
return;
}
}
Util.silentClose(file);
}
private void setUploadStatus(int val) {
synchronized(this) {
uploadStatus = val;
}
}
public int getUploadStatus() {
int ret;
synchronized(this) {
ret = uploadStatus;
}
return ret;
}
}

View File

@ -42,7 +42,11 @@ public class ClientManager {
}
public byte[] getClientKey(UUID uuid) {
return keys.get(uuid);
keyLock.readLock().lock();
byte[] ret = keys.get(uuid);
keyLock.readLock().unlock();
return ret;
}
public void revokeClientKey(UUID id) {

View File

@ -17,7 +17,7 @@ import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
public class Server extends Thread {
public class Server implements Runnable {
private static Server instance;
@ -37,40 +37,97 @@ public class Server extends Thread {
private final ClientManager clientMgr = new ClientManager();
private File directory;
private long maxQuota = 1024 * 1024; //1 MiB max
private volatile boolean running;
private volatile Thread thread;
public Server() {
setDaemon(true);
}
@Override
public void start() {
thread = new Thread(this);
thread.setName("MiniServServer");
thread.setDaemon(true);
try {
server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(port));
server.configureBlocking(false);
} catch(Throwable t) {
t.printStackTrace();
Util.silentClose(server);
server = null;
return;
}
try {
selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
} catch(Throwable t) {
t.printStackTrace();
Util.silentClose(selector);
Util.silentClose(server);
selector = null;
server = null;
return;
}
super.start();
synchronized(this) {
running = true;
}
thread.start();
}
public void stopServer() {
if(getRunning()) {
Thread thread = this.thread;
synchronized(this) {
running = false;
selector.wakeup();
}
while(thread.isAlive()) {
try {
thread.join();
} catch(InterruptedException ex) { }
}
Log.info("Miniserv server stopped");
}
}
private boolean getRunning() {
boolean ret;
synchronized(this) {
ret = running;
}
return ret;
}
@Override
public void run() {
boolean running = true;
while(running) {
while(getRunning()) {
try {
loopUnsafe();
} catch(Throwable t) {
t.printStackTrace();
running = false;
Log.errorEx("Miniserv Server crashed", t);
break;
}
}
synchronized(this) {
running = false;
}
for(ServerClient cli: clientList)
Util.silentClose(cli.getChannel());
clientList.clear();
clientMap.clear();
Util.silentClose(selector);
Util.silentClose(server);
selector = null;
server = null;
thread = null;
}
private void loopUnsafe() throws Throwable {
@ -89,9 +146,8 @@ public class Server extends Thread {
if(chan != null) {
chan.configureBlocking(false);
chan.register(selector, SelectionKey.OP_READ);
ServerClient toAdd = new ServerClient(chan, selector);
clientMap.put(chan, toAdd);
clientList.add(toAdd);
toAdd.onConnect();
@ -110,7 +166,7 @@ public class Server extends Thread {
if(read < 0)
cli.setShouldRemove(); //End of stream
else {
else if(read > 0) {
readBuffer.position(0);
readBuffer.limit(read);
cli.readyRead(readBuffer);

View File

@ -9,6 +9,8 @@ import net.montoyo.wd.utilities.Log;
import net.montoyo.wd.utilities.Util;
import java.io.*;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.UUID;
@ -32,6 +34,10 @@ public class ServerClient extends AbstractClient {
ServerClient(SocketChannel s, Selector ss) {
socket = s;
selector = ss;
try {
selKey = socket.register(selector, SelectionKey.OP_READ);
} catch(ClosedChannelException ex) {}
}
@Override
@ -92,6 +98,7 @@ public class ServerClient extends AbstractClient {
int len = dis.readByte() & 0xFF;
byte[] mac = new byte[len];
dis.readFully(mac);
if(Server.getInstance().getClientManager().verifyClient(uuid, challenge, mac)) {
Log.info("Client with UUID %s authenticated successfully", uuid.toString());
@ -136,21 +143,21 @@ public class ServerClient extends AbstractClient {
OutgoingPacket rep = new OutgoingPacket();
rep.writeByte(PacketID.BEGIN_FILE_UPLOAD.ordinal());
if(isFileNameInvalid(fname)) {
if(Util.isFileNameInvalid(fname)) {
Log.warning("Client %s tried to upload a file with a bad name", uuid.toString());
rep.writeByte(1);
rep.writeByte(Constants.FUPA_STATUS_BAD_NAME);
} else if(size <= 0) {
Log.warning("Client %s tried to upload a file an invalid size", uuid.toString());
rep.writeByte(2);
rep.writeByte(Constants.FUPA_STATUS_INVALID_SIZE);
} else if(quota + size > Server.getInstance().getMaxQuota())
rep.writeByte(3);
rep.writeByte(Constants.FUPA_STATUS_EXCEEDS_QUOTA);
else if(currentFile != null || sendingFile)
rep.writeByte(4);
rep.writeByte(Constants.FUPA_STATUS_OCCUPIED);
else {
File fle = new File(userDir, fname);
if(fle.exists())
rep.writeByte(5);
rep.writeByte(Constants.FUPA_STATUS_FILE_EXISTS);
else {
try {
currentFile = new FileOutputStream(fle);
@ -160,7 +167,7 @@ public class ServerClient extends AbstractClient {
rep.writeByte(0); //OK
} catch(IOException ex) {
Log.warningEx("IOException while uploading file %s from user %s", ex, fname, uuid.toString());
rep.writeByte(6);
rep.writeByte(Constants.FUPA_STATUS_INTERNAL_ERROR);
}
}
}
@ -175,14 +182,14 @@ public class ServerClient extends AbstractClient {
int len = dis.readShort() & 0xFFFF;
if(len <= 0) {
//Aborted by user
finishUpload(1);
finishUpload(Constants.FUPA_STATUS_USER_ABORT);
return;
}
currentFileSize += (long) len;
if(currentFileSize > currentFileExpectedSize) {
//Exceeded expected size
finishUpload(2);
finishUpload(Constants.FUPA_STATUS_LIER);
return;
}
@ -190,7 +197,7 @@ public class ServerClient extends AbstractClient {
currentFile.write(getCurrentPacketRawData(), 3, len);
} catch(IOException ex) {
Log.warningEx("Client %s encountered an IOException while uploading some file", ex, uuid.toString());
finishUpload(3);
finishUpload(Constants.FUPA_STATUS_INTERNAL_ERROR);
currentFileSize -= (long) len;
return;
}
@ -210,8 +217,8 @@ public class ServerClient extends AbstractClient {
OutgoingPacket rep = new OutgoingPacket();
rep.writeByte(PacketID.GET_FILE.ordinal());
if(isFileNameInvalid(fname))
rep.writeByte(1);
if(Util.isFileNameInvalid(fname))
rep.writeByte(Constants.GETF_STATUS_BAD_NAME);
else {
UUID user = new UUID(msb, lsb);
File fle = new File(Server.getInstance().getDirectory(), user.toString() + File.separatorChar + fname);
@ -220,7 +227,7 @@ public class ServerClient extends AbstractClient {
rep.setOnFinishAction(new SendFileCallback(fle));
sendingFile = true;
} catch(FileNotFoundException ex) {
rep.writeByte(2);
rep.writeByte(Constants.GETF_STATUS_NOT_FOUND);
}
}
@ -228,10 +235,6 @@ public class ServerClient extends AbstractClient {
}
}
private static boolean isFileNameInvalid(String fname) {
return fname.isEmpty() || fname.length() > 64 || fname.charAt(0) == '.' || fname.indexOf('/') >= 0 || fname.indexOf('\\') >= 0;
}
private void finishUpload(int status) {
if(currentFile != null) {
OutgoingPacket pkt = new OutgoingPacket();
@ -274,7 +277,7 @@ public class ServerClient extends AbstractClient {
OutgoingPacket pkt = new OutgoingPacket();
pkt.writeByte(PacketID.GET_FILE.ordinal());
pkt.writeByte(3); //Read error
pkt.writeByte(Constants.GETF_STATUS_INTERNAL_ERROR); //Read error
sendPacket(pkt);
Util.silentClose(fis);
@ -292,7 +295,7 @@ public class ServerClient extends AbstractClient {
pkt.writeByte(PacketID.FILE_PART.ordinal());
pkt.writeShort(rd);
pkt.writeBytes(FILE_UPLOAD_BUFFER);
pkt.writeBytes(FILE_UPLOAD_BUFFER, 0, rd);
sendPacket(pkt);
}

View File

@ -6,12 +6,13 @@ package net.montoyo.wd.net;
import io.netty.buffer.ByteBuf;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.miniserv.client.Client;
import net.montoyo.wd.utilities.Log;
@Message(messageId = 11, side = Side.CLIENT)
public class CMessageMiniservKey implements IMessage {
public class CMessageMiniservKey implements IMessage, Runnable {
private byte[] encryptedKey;
@ -24,24 +25,22 @@ public class CMessageMiniservKey implements IMessage {
@Override
public void fromBytes(ByteBuf buf) {
encryptedKey = new byte[buf.readByte() & 0xFF];
encryptedKey = new byte[buf.readShort() & 0xFFFF];
buf.readBytes(encryptedKey);
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeByte(encryptedKey.length);
buf.writeShort(encryptedKey.length);
buf.writeBytes(encryptedKey);
}
public static class Handler implements IMessageHandler<CMessageMiniservKey, IMessage> {
@Override
public IMessage onMessage(CMessageMiniservKey message, MessageContext ctx) {
//TODO: Start client thread
return null;
@Override
public void run() {
if(Client.getInstance().decryptKey(encryptedKey)) {
Log.info("Successfully received and decrypted key, starting miniserv client...");
WebDisplays.PROXY.startMiniServClient();
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.net;
import io.netty.buffer.ByteBuf;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.miniserv.client.Client;
@Message(messageId = 12, side = Side.CLIENT)
public class CMessageServerInfo implements IMessage, Runnable {
private int miniservPort;
public CMessageServerInfo() {
}
public CMessageServerInfo(int msPort) {
miniservPort = msPort;
}
@Override
public void fromBytes(ByteBuf buf) {
miniservPort = buf.readShort() & 0xFFFF;
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeShort(miniservPort);
}
@Override
public void run() {
WebDisplays.PROXY.setMiniservClientPort(miniservPort);
if(miniservPort > 0)
WebDisplays.NET_HANDLER.sendToServer(Client.getInstance().beginConnection());
}
}

View File

@ -29,6 +29,7 @@ public abstract class Messages {
l.add(CMessageJSResponse.class);
l.add(SMessageMiniservConnect.class);
l.add(CMessageMiniservKey.class);
l.add(CMessageServerInfo.class);
messages = l.toArray(new Class[0]);
}

View File

@ -9,6 +9,8 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;
import net.montoyo.wd.miniserv.server.ClientManager;
import net.montoyo.wd.miniserv.server.Server;
@Message(messageId = 10, side = Side.SERVER)
public class SMessageMiniservConnect implements IMessage {
@ -26,29 +28,31 @@ public class SMessageMiniservConnect implements IMessage {
@Override
public void fromBytes(ByteBuf buf) {
int sz = buf.readByte() & 0xFF;
int sz = buf.readShort() & 0xFFFF;
modulus = new byte[sz];
buf.readBytes(modulus);
sz = buf.readByte() & 0xFF;
sz = buf.readShort() & 0xFFFF;
exponent = new byte[sz];
buf.readBytes(exponent);
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeByte(modulus.length);
buf.writeShort(modulus.length);
buf.writeBytes(modulus);
buf.writeByte(exponent.length);
buf.writeShort(exponent.length);
buf.writeBytes(exponent);
}
public static class Handler implements IMessageHandler<SMessageMiniservConnect, IMessage> {
@Override
public IMessage onMessage(SMessageMiniservConnect message, MessageContext ctx) {
//TODO: Generate key
return null;
public IMessage onMessage(SMessageMiniservConnect msg, MessageContext ctx) {
ClientManager cliMgr = Server.getInstance().getClientManager();
byte[] encKey = cliMgr.encryptClientKey(ctx.getServerHandler().player.getGameProfile().getId(), msg.modulus, msg.exponent);
return encKey == null ? null : new CMessageMiniservKey(encKey);
}
}

View File

@ -224,4 +224,7 @@ public abstract class Util {
return str.contains("://") ? str : ("http://" + str);
}
public static boolean isFileNameInvalid(String fname) {
return fname.isEmpty() || fname.length() > 64 || fname.charAt(0) == '.' || fname.indexOf('/') >= 0 || fname.indexOf('\\') >= 0;
}
}