* More work on miniserv
This commit is contained in:
parent
d77587ca38
commit
a83601b427
135
src/main/java/net/montoyo/wd/miniserv/AbstractClient.java
Normal file
135
src/main/java/net/montoyo/wd/miniserv/AbstractClient.java
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 BARBOTIN Nicolas
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.montoyo.wd.miniserv;
|
||||||
|
|
||||||
|
import net.montoyo.wd.utilities.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
|
public abstract class AbstractClient {
|
||||||
|
|
||||||
|
private final ByteBuffer sendBuffer = ByteBuffer.allocateDirect(8192);
|
||||||
|
private final ArrayDeque<OutgoingPacket> sendQueue = new ArrayDeque<>();
|
||||||
|
private final PacketReader packetReader = new PacketReader();
|
||||||
|
private final PacketWriter packetWriter = new PacketWriter();
|
||||||
|
private final Method[] packetHandlers = new Method[PacketID.values().length];
|
||||||
|
protected SocketChannel socket;
|
||||||
|
protected Selector selector;
|
||||||
|
private SelectionKey writeKey;
|
||||||
|
|
||||||
|
public AbstractClient() {
|
||||||
|
sendBuffer.limit(0);
|
||||||
|
|
||||||
|
Method[] methods = getClass().getMethods();
|
||||||
|
for(Method m: methods) {
|
||||||
|
PacketHandler ph = m.getAnnotation(PacketHandler.class);
|
||||||
|
|
||||||
|
if(ph != null) {
|
||||||
|
if(packetHandlers[ph.value().ordinal()] != null)
|
||||||
|
Log.warning("AbstractClient: several packet handlers for %s, ignoring %s", ph.value().toString(), m.getName());
|
||||||
|
else if(m.getParameterCount() != 1 || m.getParameterTypes()[0] != DataInputStream.class)
|
||||||
|
Log.warning("AbstractClient: found invalid packet handler %s", m.getName());
|
||||||
|
else
|
||||||
|
packetHandlers[ph.value().ordinal()] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < packetHandlers.length; i++) {
|
||||||
|
if(packetHandlers[i] == null)
|
||||||
|
Log.warning("AbstractClient: no packet handler for %s", PacketID.fromInt(i).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onWriteError();
|
||||||
|
|
||||||
|
public void readyRead(ByteBuffer bb) {
|
||||||
|
while(bb.remaining() > 0) {
|
||||||
|
if(packetReader.readFrom(bb)) { //End of packet
|
||||||
|
byte[] pkt = packetReader.getPacketData();
|
||||||
|
|
||||||
|
if(pkt != null) {
|
||||||
|
try {
|
||||||
|
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(pkt));
|
||||||
|
int pid = dis.readByte() & 0xFF;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(IOException ex) {
|
||||||
|
Log.warningEx("IOException while trying to handle packet", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packetReader.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readyWrite() throws Throwable {
|
||||||
|
if(sendBuffer.remaining() > 0 || fillSendBuffer()) {
|
||||||
|
if(socket.write(sendBuffer) < 0)
|
||||||
|
onWriteError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fillSendBuffer() {
|
||||||
|
sendBuffer.clear();
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(packetWriter.writeTo(sendBuffer)) {
|
||||||
|
OutgoingPacket pkt;
|
||||||
|
synchronized(sendQueue) {
|
||||||
|
pkt = sendQueue.poll();
|
||||||
|
|
||||||
|
if(pkt == null) {
|
||||||
|
if(writeKey != null) {
|
||||||
|
writeKey.cancel();
|
||||||
|
writeKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendBuffer.remaining() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packetWriter.reset(pkt.finish());
|
||||||
|
}
|
||||||
|
} while(sendBuffer.remaining() > 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
src/main/java/net/montoyo/wd/miniserv/KeyParameters.java
Normal file
15
src/main/java/net/montoyo/wd/miniserv/KeyParameters.java
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 BARBOTIN Nicolas
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.montoyo.wd.miniserv;
|
||||||
|
|
||||||
|
public abstract class KeyParameters {
|
||||||
|
|
||||||
|
public static final int RSA_KEY_SIZE = 2048;
|
||||||
|
public static final int KEY_SIZE = 32;
|
||||||
|
public static final int CHALLENGE_SIZE = 32;
|
||||||
|
public static final String MAC_ALGORITHM = "HmacSHA256";
|
||||||
|
public static final String RSA_CIPHER = "RSA/ECB/PKCS1Padding";
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/java/net/montoyo/wd/miniserv/PacketHandler.java
Normal file
18
src/main/java/net/montoyo/wd/miniserv/PacketHandler.java
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 BARBOTIN Nicolas
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.montoyo.wd.miniserv;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface PacketHandler {
|
||||||
|
|
||||||
|
PacketID value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ package net.montoyo.wd.miniserv;
|
||||||
|
|
||||||
public enum PacketID {
|
public enum PacketID {
|
||||||
|
|
||||||
|
AUTHENTICATE, //C->S and S->C
|
||||||
PING, //C->S and S->C
|
PING, //C->S and S->C
|
||||||
BEGIN_FILE_UPLOAD, //C->S
|
BEGIN_FILE_UPLOAD, //C->S
|
||||||
FILE_PART, //C->S and S->C
|
FILE_PART, //C->S and S->C
|
||||||
|
|
|
||||||
163
src/main/java/net/montoyo/wd/miniserv/client/Client.java
Normal file
163
src/main/java/net/montoyo/wd/miniserv/client/Client.java
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 BARBOTIN Nicolas
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.montoyo.wd.miniserv.client;
|
||||||
|
|
||||||
|
import net.montoyo.wd.miniserv.*;
|
||||||
|
import net.montoyo.wd.net.SMessageMiniservConnect;
|
||||||
|
import net.montoyo.wd.utilities.Log;
|
||||||
|
import net.montoyo.wd.utilities.Util;
|
||||||
|
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
|
public class Client extends AbstractClient implements Runnable {
|
||||||
|
|
||||||
|
private static Client instance;
|
||||||
|
|
||||||
|
public static Client getInstance() {
|
||||||
|
if(instance == null)
|
||||||
|
instance = new Client();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
private KeyPair keyPair;
|
||||||
|
private byte[] key;
|
||||||
|
private SocketAddress address;
|
||||||
|
private boolean running = true;
|
||||||
|
private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(8192);
|
||||||
|
private final Thread thread = new Thread(this);
|
||||||
|
private boolean authenticated = false;
|
||||||
|
|
||||||
|
public SMessageMiniservConnect beginConnection() {
|
||||||
|
if(keyPair == null) {
|
||||||
|
try {
|
||||||
|
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
|
||||||
|
keygen.initialize(KeyParameters.RSA_KEY_SIZE);
|
||||||
|
keyPair = keygen.generateKeyPair();
|
||||||
|
} catch(NoSuchAlgorithmException ex) {
|
||||||
|
Log.warningEx("RSA is unsupported?!?!", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
|
||||||
|
return new SMessageMiniservConnect(pubKey.getModulus().toByteArray(), pubKey.getPublicExponent().toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean decryptKey(byte[] encKey) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(KeyParameters.RSA_CIPHER);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), random);
|
||||||
|
key = cipher.doFinal(encKey);
|
||||||
|
return true;
|
||||||
|
} catch(NoSuchAlgorithmException | NoSuchPaddingException ex) {
|
||||||
|
Log.warningEx("%s unsupported...", ex, KeyParameters.RSA_CIPHER);
|
||||||
|
} catch(InvalidKeyException ex) {
|
||||||
|
Log.warningEx("The generated key is invalid...", ex);
|
||||||
|
} catch(IllegalBlockSizeException | BadPaddingException ex) {
|
||||||
|
Log.warningEx("Could not decrypt key", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] authenticate(byte[] challenge) {
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance(KeyParameters.MAC_ALGORITHM);
|
||||||
|
mac.init(new SecretKeySpec(key, KeyParameters.MAC_ALGORITHM));
|
||||||
|
return mac.doFinal(challenge);
|
||||||
|
} catch(NoSuchAlgorithmException ex) {
|
||||||
|
Log.warningEx("%s unsupported...", ex, KeyParameters.MAC_ALGORITHM);
|
||||||
|
} catch(InvalidKeyException ex) {
|
||||||
|
Log.warningEx("The key given by the server is invalid", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(SocketAddress addr) {
|
||||||
|
address = addr;
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
socket = SocketChannel.open();
|
||||||
|
socket.connect(address);
|
||||||
|
socket.configureBlocking(false);
|
||||||
|
|
||||||
|
selector = Selector.open();
|
||||||
|
socket.register(selector, SelectionKey.OP_READ);
|
||||||
|
} catch(IOException ex) {
|
||||||
|
Log.errorEx("Couldn't start client", ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(running) {
|
||||||
|
try {
|
||||||
|
unsafeLoop();
|
||||||
|
} catch(Throwable t) {
|
||||||
|
Log.errorEx("MiniServ Client crashed", t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.silentClose(socket);
|
||||||
|
Util.silentClose(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unsafeLoop() throws Throwable {
|
||||||
|
selector.select();
|
||||||
|
|
||||||
|
for(SelectionKey key: selector.selectedKeys()) {
|
||||||
|
if(key.isReadable()) {
|
||||||
|
readBuffer.clear();
|
||||||
|
int rd = socket.read(readBuffer);
|
||||||
|
|
||||||
|
if(rd <= 0) {
|
||||||
|
Log.warning("Connection was closed, stopping...");
|
||||||
|
running = false;
|
||||||
|
} else {
|
||||||
|
readBuffer.position(0);
|
||||||
|
readBuffer.limit(rd);
|
||||||
|
readyRead(readBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key.isWritable()) {
|
||||||
|
try {
|
||||||
|
readyWrite();
|
||||||
|
} catch(Throwable t) {
|
||||||
|
Log.errorEx("Caught error while sending data to miniserv, stopping...", t);
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onWriteError() {
|
||||||
|
running = false;
|
||||||
|
Log.error("Write error, stopping...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PacketHandler(PacketID.AUTHENTICATE)
|
||||||
|
public void handleAuth(DataInputStream dis) {
|
||||||
|
//TODO: Do some stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 BARBOTIN Nicolas
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.montoyo.wd.miniserv.server;
|
||||||
|
|
||||||
|
import net.montoyo.wd.miniserv.KeyParameters;
|
||||||
|
import net.montoyo.wd.utilities.Log;
|
||||||
|
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
public class ClientManager {
|
||||||
|
|
||||||
|
private final SecureRandom random = new SecureRandom(); //SecureRandom is thread safe
|
||||||
|
private final HashMap<UUID, byte[]> keys = new HashMap<>();
|
||||||
|
private final ReentrantReadWriteLock keyLock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
public byte[] getClientKey(UUID uuid) {
|
||||||
|
keyLock.readLock().lock();
|
||||||
|
byte[] key = keys.get(uuid);
|
||||||
|
keyLock.readLock().unlock();
|
||||||
|
|
||||||
|
if(key == null) {
|
||||||
|
key = new byte[KeyParameters.KEY_SIZE];
|
||||||
|
random.nextBytes(key);
|
||||||
|
|
||||||
|
keyLock.writeLock().lock();
|
||||||
|
keys.put(uuid, key);
|
||||||
|
keyLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void revokeClientKey(UUID id) {
|
||||||
|
keyLock.writeLock().lock();
|
||||||
|
keys.remove(id);
|
||||||
|
keyLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] generateChallenge() {
|
||||||
|
byte[] ret = new byte[KeyParameters.CHALLENGE_SIZE];
|
||||||
|
random.nextBytes(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifyClient(UUID client, byte[] challenge, byte[] hmac) {
|
||||||
|
keyLock.readLock().lock();
|
||||||
|
byte[] key = keys.get(client);
|
||||||
|
keyLock.readLock().unlock();
|
||||||
|
|
||||||
|
if(challenge == null || hmac == null || key == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance(KeyParameters.MAC_ALGORITHM);
|
||||||
|
mac.init(new SecretKeySpec(key, KeyParameters.MAC_ALGORITHM));
|
||||||
|
byte[] result = mac.doFinal(challenge);
|
||||||
|
|
||||||
|
return Arrays.equals(hmac, result);
|
||||||
|
} catch(NoSuchAlgorithmException ex) {
|
||||||
|
Log.warningEx("%s is not supported?!?!", ex, KeyParameters.MAC_ALGORITHM);
|
||||||
|
} catch(InvalidKeyException ex) {
|
||||||
|
Log.warningEx("The generated key is invalid", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encryptClientKey(UUID client, byte[] modulus, byte[] exponent) {
|
||||||
|
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
|
||||||
|
|
||||||
|
try {
|
||||||
|
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance(KeyParameters.RSA_CIPHER);
|
||||||
|
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key, random);
|
||||||
|
return cipher.doFinal(getClientKey(client));
|
||||||
|
} catch(NoSuchAlgorithmException | NoSuchPaddingException ex) {
|
||||||
|
Log.warningEx("%s is not supported?!?!", ex, KeyParameters.RSA_CIPHER);
|
||||||
|
} catch(InvalidKeySpecException | InvalidKeyException ex) {
|
||||||
|
Log.warningEx("A client sent a malicious key", ex);
|
||||||
|
} catch(IllegalBlockSizeException | BadPaddingException ex) {
|
||||||
|
Log.warningEx("Could not encrypt client key", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
package net.montoyo.wd.miniserv.server;
|
package net.montoyo.wd.miniserv.server;
|
||||||
|
|
||||||
import net.montoyo.wd.utilities.Log;
|
import net.montoyo.wd.utilities.Log;
|
||||||
|
import net.montoyo.wd.utilities.Util;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
@ -32,8 +33,8 @@ public class Server extends Thread {
|
||||||
public void start() {
|
public void start() {
|
||||||
try {
|
try {
|
||||||
server = ServerSocketChannel.open();
|
server = ServerSocketChannel.open();
|
||||||
|
server.bind(new InetSocketAddress(port));
|
||||||
server.configureBlocking(false);
|
server.configureBlocking(false);
|
||||||
server.socket().bind(new InetSocketAddress(port));
|
|
||||||
|
|
||||||
selector = Selector.open();
|
selector = Selector.open();
|
||||||
server.register(selector, SelectionKey.OP_ACCEPT);
|
server.register(selector, SelectionKey.OP_ACCEPT);
|
||||||
|
|
@ -81,7 +82,9 @@ public class Server extends Thread {
|
||||||
clientMap.put(chan, toAdd);
|
clientMap.put(chan, toAdd);
|
||||||
clientList.add(toAdd);
|
clientList.add(toAdd);
|
||||||
}
|
}
|
||||||
} else if(key.isReadable()) {
|
}
|
||||||
|
|
||||||
|
if(key.isReadable()) {
|
||||||
ServerClient cli = clientMap.get(key.channel());
|
ServerClient cli = clientMap.get(key.channel());
|
||||||
|
|
||||||
if(cli == null)
|
if(cli == null)
|
||||||
|
|
@ -103,7 +106,9 @@ public class Server extends Thread {
|
||||||
cli.setShouldRemove();
|
cli.setShouldRemove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(key.isWritable()) {
|
}
|
||||||
|
|
||||||
|
if(key.isWritable()) {
|
||||||
ServerClient cli = clientMap.get(key.channel());
|
ServerClient cli = clientMap.get(key.channel());
|
||||||
|
|
||||||
if(cli == null)
|
if(cli == null)
|
||||||
|
|
@ -130,10 +135,7 @@ public class Server extends Thread {
|
||||||
private void removeClient(ServerClient cli) {
|
private void removeClient(ServerClient cli) {
|
||||||
clientMap.remove(cli.getChannel());
|
clientMap.remove(cli.getChannel());
|
||||||
clientList.remove(cli);
|
clientList.remove(cli);
|
||||||
|
Util.silentClose(cli.getChannel());
|
||||||
try {
|
|
||||||
cli.getChannel().close();
|
|
||||||
} catch(Throwable t) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,95 +4,26 @@
|
||||||
|
|
||||||
package net.montoyo.wd.miniserv.server;
|
package net.montoyo.wd.miniserv.server;
|
||||||
|
|
||||||
import net.montoyo.wd.miniserv.OutgoingPacket;
|
import net.montoyo.wd.miniserv.*;
|
||||||
import net.montoyo.wd.miniserv.PacketID;
|
|
||||||
import net.montoyo.wd.miniserv.PacketReader;
|
|
||||||
import net.montoyo.wd.miniserv.PacketWriter;
|
|
||||||
import net.montoyo.wd.utilities.Log;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.ClosedChannelException;
|
|
||||||
import java.nio.channels.SelectionKey;
|
|
||||||
import java.nio.channels.Selector;
|
import java.nio.channels.Selector;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.ArrayDeque;
|
|
||||||
|
|
||||||
public class ServerClient {
|
public class ServerClient extends AbstractClient {
|
||||||
|
|
||||||
private final SocketChannel socket;
|
|
||||||
private final Selector selector;
|
|
||||||
private SelectionKey writeKey;
|
|
||||||
private boolean remove;
|
private boolean remove;
|
||||||
private final ByteBuffer sendBuffer = ByteBuffer.allocateDirect(8192);
|
private boolean isAuthenticated;
|
||||||
private final ArrayDeque<OutgoingPacket> sendQueue = new ArrayDeque<>();
|
|
||||||
private final PacketReader packetReader = new PacketReader();
|
|
||||||
private final PacketWriter packetWriter = new PacketWriter();
|
|
||||||
|
|
||||||
ServerClient(SocketChannel s, Selector ss) {
|
ServerClient(SocketChannel s, Selector ss) {
|
||||||
socket = s;
|
socket = s;
|
||||||
selector = ss;
|
selector = ss;
|
||||||
sendBuffer.limit(0); //Set empty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void readyRead(ByteBuffer bb) {
|
@Override
|
||||||
while(bb.remaining() > 0) {
|
protected void onWriteError() {
|
||||||
if(packetReader.readFrom(bb)) { //End of packet
|
remove = true;
|
||||||
byte[] pkt = packetReader.getPacketData();
|
|
||||||
if(pkt != null) {
|
|
||||||
try {
|
|
||||||
handlePacket(pkt);
|
|
||||||
} catch(IOException ex) {
|
|
||||||
Log.warningEx("IOException while trying to handle packet", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
packetReader.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePacket(byte[] pkt) throws IOException {
|
|
||||||
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(pkt));
|
|
||||||
PacketID pid = PacketID.fromInt(dis.readByte());
|
|
||||||
|
|
||||||
if(pid == null) {
|
|
||||||
Log.warning("Caught packet with invalid ID from client");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OutgoingPacket response = null;
|
|
||||||
|
|
||||||
switch(pid) {
|
|
||||||
case PING:
|
|
||||||
response = new OutgoingPacket();
|
|
||||||
response.writeByte(PacketID.PING.ordinal());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BEGIN_FILE_UPLOAD:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FILE_PART:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GET_FILE:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(response != null)
|
|
||||||
sendPacket(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void readyWrite() throws Throwable {
|
|
||||||
if(sendBuffer.remaining() > 0 || fillSendBuffer()) {
|
|
||||||
if(socket.write(sendBuffer) < 0)
|
|
||||||
remove = true;
|
|
||||||
} else if(writeKey != null) {
|
|
||||||
writeKey.cancel();
|
|
||||||
writeKey = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setShouldRemove() {
|
void setShouldRemove() {
|
||||||
|
|
@ -107,32 +38,16 @@ public class ServerClient {
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean fillSendBuffer() {
|
@PacketHandler(PacketID.AUTHENTICATE)
|
||||||
sendBuffer.clear();
|
public void handleAuthPacket(DataInputStream dis) throws IOException {
|
||||||
|
//TODO: Do some stuff
|
||||||
do {
|
|
||||||
if(packetWriter.writeTo(sendBuffer)) {
|
|
||||||
OutgoingPacket pkt = sendQueue.poll();
|
|
||||||
if(pkt == null)
|
|
||||||
return sendBuffer.remaining() > 0;
|
|
||||||
|
|
||||||
packetWriter.reset(pkt.finish());
|
|
||||||
}
|
|
||||||
} while(sendBuffer.remaining() > 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPacket(OutgoingPacket pkt) {
|
@PacketHandler(PacketID.PING)
|
||||||
sendQueue.offer(pkt);
|
public void handlePing(DataInputStream dis) {
|
||||||
|
OutgoingPacket pkt = new OutgoingPacket();
|
||||||
if(writeKey == null) {
|
pkt.writeByte(PacketID.PING.ordinal());
|
||||||
try {
|
sendPacket(pkt);
|
||||||
writeKey = socket.register(selector, SelectionKey.OP_WRITE);
|
|
||||||
} catch(ClosedChannelException ex) {
|
|
||||||
Log.warningEx("Couldn't send packet", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
src/main/java/net/montoyo/wd/net/CMessageMiniservKey.java
Normal file
47
src/main/java/net/montoyo/wd/net/CMessageMiniservKey.java
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.common.network.simpleimpl.IMessageHandler;
|
||||||
|
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
|
||||||
|
import net.minecraftforge.fml.relauncher.Side;
|
||||||
|
|
||||||
|
@Message(messageId = 11, side = Side.CLIENT)
|
||||||
|
public class CMessageMiniservKey implements IMessage {
|
||||||
|
|
||||||
|
private byte[] encryptedKey;
|
||||||
|
|
||||||
|
public CMessageMiniservKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessageMiniservKey(byte[] key) {
|
||||||
|
encryptedKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromBytes(ByteBuf buf) {
|
||||||
|
encryptedKey = new byte[buf.readByte() & 0xFF];
|
||||||
|
buf.readBytes(encryptedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toBytes(ByteBuf buf) {
|
||||||
|
buf.writeByte(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class Messages {
|
public abstract class Messages {
|
||||||
|
|
||||||
private static DefaultHandler DEFAULT_HANDLER = new DefaultHandler();
|
private static DefaultHandler DEFAULT_HANDLER = new DefaultHandler();
|
||||||
private static Class<? extends IMessage>[] messages;
|
private static Class<? extends IMessage>[] messages;
|
||||||
|
|
@ -27,6 +27,8 @@ public class Messages {
|
||||||
l.add(SMessagePadCtrl.class);
|
l.add(SMessagePadCtrl.class);
|
||||||
l.add(SMessageRedstoneCtrl.class);
|
l.add(SMessageRedstoneCtrl.class);
|
||||||
l.add(CMessageJSResponse.class);
|
l.add(CMessageJSResponse.class);
|
||||||
|
l.add(SMessageMiniservConnect.class);
|
||||||
|
l.add(CMessageMiniservKey.class);
|
||||||
|
|
||||||
messages = l.toArray(new Class[0]);
|
messages = l.toArray(new Class[0]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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.common.network.simpleimpl.IMessageHandler;
|
||||||
|
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
|
||||||
|
import net.minecraftforge.fml.relauncher.Side;
|
||||||
|
|
||||||
|
@Message(messageId = 10, side = Side.SERVER)
|
||||||
|
public class SMessageMiniservConnect implements IMessage {
|
||||||
|
|
||||||
|
private byte[] modulus;
|
||||||
|
private byte[] exponent;
|
||||||
|
|
||||||
|
public SMessageMiniservConnect() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SMessageMiniservConnect(byte[] mod, byte[] exp) {
|
||||||
|
modulus = mod;
|
||||||
|
exponent = exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fromBytes(ByteBuf buf) {
|
||||||
|
int sz = buf.readByte() & 0xFF;
|
||||||
|
modulus = new byte[sz];
|
||||||
|
buf.readBytes(modulus);
|
||||||
|
|
||||||
|
sz = buf.readByte() & 0xFF;
|
||||||
|
exponent = new byte[sz];
|
||||||
|
buf.readBytes(exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toBytes(ByteBuf buf) {
|
||||||
|
buf.writeByte(modulus.length);
|
||||||
|
buf.writeBytes(modulus);
|
||||||
|
buf.writeByte(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user