Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
public interface ShandaCipher {

static void encryptData(ByteBuf buf,
int offset,
int length) {
int offset = 0;
for (int j = 0; j < 6; j++) {
byte remember = 0;
byte dataLength = (byte) (length & 0xFF);
Expand Down Expand Up @@ -43,8 +43,8 @@ static void encryptData(ByteBuf buf,
}

static void decryptData(ByteBuf buf,
int offset,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the offset is always 0, please inline it. no need for it to be a param.

int length) {
int offset = 0;
for (int j = 1; j <= 6; j++) {
byte remember = 0;
byte dataLength = (byte) (length & 0xFF);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,32 +57,29 @@ public class ShroomAESCipher {
(byte) 0x84, (byte) 0x7F, (byte) 0x61, (byte) 0x1E, (byte) 0xCF, (byte) 0xC5, (byte) 0xD1, (byte) 0x56,
(byte) 0x3D, (byte) 0xCA, (byte) 0xF4, (byte) 0x05, (byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49};

public static final byte[] IG_SEED = {(byte) 0xf2, 0x53, (byte) 0x50, (byte) 0xc6};
public static final byte[] IG_SEED = { (byte) 0xf2, 0x53, (byte) 0x50, (byte) 0xc6 };

private static Cipher cipher = initCipher();

private final short mapleVersion;
private final Cipher cipher;
private byte[] iv;

public ShroomAESCipher(InitializationVector iv, short mapleVersion) {
private static Cipher initCipher() {
try {
cipher = Cipher.getInstance("AES");
Cipher cipher = Cipher.getInstance("AES");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the old algo, please update to the new one.
i don't remember if i pushed it to master but it's in dev for sure

cipher.init(Cipher.ENCRYPT_MODE, AES_KEY);
return cipher;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}

private final short mapleVersion;
private byte[] iv;

public ShroomAESCipher(InitializationVector iv, short mapleVersion) {
this.iv = iv.getBytes();
this.mapleVersion = mapleVersion;
}

public ShroomAESCipher(byte[] iv, short mapleVersion) {
try {
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, AES_KEY);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}

this.iv = iv;
this.mapleVersion = mapleVersion;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dori.SpringStory.connection.netty;

import com.dori.SpringStory.client.MapleClient;
import com.dori.SpringStory.connection.crypto.InitializationVector;
import com.dori.SpringStory.connection.crypto.ShroomAESCipher;
import com.dori.SpringStory.connection.packet.packets.CLogin;
import com.dori.SpringStory.constants.ServerConstants;
Expand All @@ -19,20 +20,17 @@
import static com.dori.SpringStory.connection.netty.NettyClient.CLIENT_KEY;

public interface BaseAcceptor {

private static byte getFinalRandomByteForIV(){
return (byte) Math.abs(Math.random() * 255);
}

/**
* Creating an acceptor based on ServerBootstrap.
* @param port - server port.
* @param channelPool - The pool of the server (if needed - login / chat acceptor).
* @param channel - The current channel if it's a channel acceptor.
* @param logger - The acceptor logger.
*
* @param port - server port.
* @param channelPool - The pool of the server (if needed - login / chat
* acceptor).
* @param channel - The current channel if it's a channel acceptor.
* @param logger - The acceptor logger.
*/
static void createAcceptor(int port, @Nullable Map<String, Channel> channelPool,
@Nullable MapleChannel channel, @NonNull Logger logger){
@Nullable MapleChannel channel, @NonNull Logger logger) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Expand All @@ -44,26 +42,30 @@ static void createAcceptor(int port, @Nullable Map<String, Channel> channelPool,
@Override
protected void initChannel(SocketChannel ch) {
// Updated to v95 -
byte[] siv = new byte[]{82, 48, 120, getFinalRandomByteForIV()};
byte[] riv = new byte[]{70, 114, 122, getFinalRandomByteForIV()};
InitializationVector siv = InitializationVector.generateSend();
InitializationVector riv = InitializationVector.generateReceive();
short rver = ServerConstants.VERSION;
short sver = (short) ~ServerConstants.VERSION;
// Set Decoder/Handler/Encoder -
ch.pipeline().addLast(new PacketDecoder(new ShroomAESCipher(riv, ServerConstants.VERSION)), new ChannelHandler(), new PacketEncoder(new ShroomAESCipher(siv, (short) ~ServerConstants.VERSION)));
ch.pipeline().addLast(new PacketDecoder(riv, rver),
new ChannelHandler(),
new PacketEncoder(siv, sver));
// Init Encoder outPacketMapping -
PacketEncoder.initOutPacketOpcodesHandling();
// Create new client for the connection -
MapleClient c = new MapleClient(ch);
// Init connection for the client -
logger.serverNotice(String.format("[CHAT] Opened session with %s in Acceptor", c.getIP()));
c.write(CLogin.sendConnect(siv,riv));
c.write(CLogin.sendConnect(siv.getBytes(), riv.getBytes()));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your Initialization vector save int and start convert to bytes each time you call getBytes, it's redundant and expensive thus i've removed it and kept it as byte array

// If we get a ChannelPool, then it will add the IP to the pool -
if(channelPool != null){
if (channelPool != null) {
// Add to the channel pool -
channelPool.put(c.getIP(), ch);
}
// ChannelInitializer attributes -
ch.attr(CLIENT_KEY).set(c);

//EventManager.addFixedRateEvent(c::sendPing, 0, 10000);
// EventManager.addFixedRateEvent(c::sendPing, 0, 10000);
}
});
// Adding channel options -
Expand All @@ -72,8 +74,9 @@ protected void initChannel(SocketChannel ch) {
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
// If we get a MapleChannel then print the listening to the channel and port -
if(channel != null){
logger.notice(String.format("Channel %d-%d listening on port %d", channel.getWorldId(), channel.getChannelId(), channel.getPort()));
if (channel != null) {
logger.notice(String.format("Channel %d-%d listening on port %d", channel.getWorldId(),
channel.getChannelId(), channel.getPort()));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary dropping line

}
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.dori.SpringStory.connection.netty;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;

import com.dori.SpringStory.connection.packet.OutPacket;

public class BroadcastSet<T extends NettyClient> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after thinking on what you said yesterday -
we can just keep the lock as static rather then instance base and each time you get to the encoder - call this util class that will lock, get list of clients and then do the same business logic

private final Map<Integer, T> clients;
private final ReentrantReadWriteLock lock;

public BroadcastSet() {
this.clients = new HashMap<>();
// A fair lock is not required as locks will be short-lived
this.lock = new ReentrantReadWriteLock(false);
}

/**
* Broadcasts the given packet to all clients in the set
*
* @param packet
*/
public void broadcast(OutPacket packet) {
ReadLock lock = this.lock.readLock();
try {
for (T client : clients.values()) {
client.write((OutPacket) packet.retainedDuplicate());
}
} finally {
packet.release();
lock.unlock();
}
}

/**
* Broadcasts the given packet to all clients in the set, excluding the client with the given id
*
* @param packet
* @param excludeId
*/
public void broadcastFilter(OutPacket packet, int excludeId) {
ReadLock lock = this.lock.readLock();
// Atmost one client which would be filtered in theory
// TODO: check if that is a good idea, because that's not always
// if the exclude id is another char
if (clients.size() <= 1) {
packet.release();
return;
}
try {
for (Entry<Integer, T> e : clients.entrySet()) {
if (e.getKey() != excludeId) {
e.getValue().write((OutPacket) packet.retainedDuplicate());
}
}
} finally {
packet.release();
lock.unlock();
}
}

public void addClient(int id, T client) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with what i suggested it's not needed anymore

lock.writeLock().lock();
try {
clients.put(id, client);
} finally {
lock.writeLock().unlock();
}
}

public void removeClient(int id) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with what i suggested it's not needed anymore

lock.writeLock().lock();
try {
clients.remove(id);
} finally {
lock.writeLock().unlock();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import static com.dori.SpringStory.constants.ServerConstants.LOGIN_PORT;

// Taken from http://netty.io/wiki/user-guide-for-4.x.html
public class LoginAcceptor implements Runnable{
public class LoginAcceptor implements Runnable {
// Channel pool -
public static Map<String, Channel> channelPool = new HashMap<>();
// Logger -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import io.netty.util.AttributeKey;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.locks.ReentrantLock;

/**
* Abstraction for Netty channels that contains some attribute keys
Expand Down Expand Up @@ -53,12 +52,6 @@ public class NettyClient {
*/
protected final Channel ch;

/**
* Lock regarding the encoding of packets to be sent to remote
* sessions.
*/
private final ReentrantLock lock;

/**
* InPacket object for this specific session since this can help
* scaling compared to keeping OutPacket for each session.
Expand All @@ -68,7 +61,6 @@ public class NettyClient {
public NettyClient() {
ch = null;
r = new InPacket();
lock = new ReentrantLock(true); // note: lock is fair to ensure logical sequence is maintained server-side
}

/**
Expand All @@ -79,7 +71,6 @@ public NettyClient() {
public NettyClient(Channel c) {
ch = c;
r = new InPacket();
lock = new ReentrantLock(true); // note: lock is fair to ensure logical sequence is maintained server-side
}

/**
Expand Down Expand Up @@ -131,21 +122,4 @@ public void close() {
public String getIP() {
return ch.remoteAddress().toString().split(":")[0].substring(1);
}

/**
* Acquires the encoding state for this specific send IV. This is to
* prevent multiple encoding states to be possible at the same time. If
* allowed, the send IV would mutate to an unusable IV and the session would
* be dropped as a result.
*/
public final void acquireEncoderState() {
lock.lock();
}

/**
* Releases the encoding state for this specific send IV.
*/
public final void releaseEncodeState() {
lock.unlock();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.dori.SpringStory.connection.netty;

import com.dori.SpringStory.connection.crypto.InitializationVector;
import com.dori.SpringStory.connection.crypto.ShandaCipher;
import com.dori.SpringStory.connection.crypto.ShroomAESCipher;
import com.dori.SpringStory.connection.packet.InPacket;
import com.dori.SpringStory.connection.packet.Packet;
import com.dori.SpringStory.logger.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
Expand All @@ -15,14 +17,15 @@

public class PacketDecoder extends ReplayingDecoder<Integer> {
private static final Logger log = new Logger(PacketDecoder.class);
public static final int MAX_PACKET_LEN = 2 * 4096;
private final ShroomAESCipher cipher;

private final ShroomAESCipher receiveCypher;
public PacketDecoder(InitializationVector iv, short version) {
this(new ShroomAESCipher(iv, version));
}

public PacketDecoder(ShroomAESCipher receiveCypher) {
public PacketDecoder(ShroomAESCipher cipher) {
super(-1);

this.receiveCypher = receiveCypher;
this.cipher = cipher;
}

@Override
Expand All @@ -31,17 +34,17 @@ protected void decode(ChannelHandlerContext chc,
List<Object> out) {
int packetLength = state();
if(packetLength == -1) {
final int packetLen = receiveCypher.decodeHeader(inPacketData.readIntLE());
final int packetLen = cipher.decodeHeader(inPacketData.readIntLE());
checkpoint(packetLen);
if(packetLen < 0 || packetLen > MAX_PACKET_LEN)
if(packetLen < 0 || packetLen > Packet.MAX_PKT_LEN)
throw new EncoderException("Packet length out of limits");
packetLength = packetLen;
}
ByteBuf pktBuf = inPacketData.readRetainedSlice(packetLength);
this.checkpoint(-1);
receiveCypher.crypt(pktBuf, 0, packetLength);
cipher.crypt(pktBuf, 0, packetLength);
if (ENABLE_ENCRYPTION) {
ShandaCipher.decryptData(pktBuf, packetLength);
ShandaCipher.decryptData(pktBuf, 0, packetLength);
}
out.add(new InPacket(pktBuf));
}
Expand Down
Loading