Newer
Older
KeeperJerry_Launcher / LaunchServer / source / response / ResponseThread.java
@KeeperJerry KeeperJerry on 27 Jun 2020 6 KB Рефактор кода
package launchserver.response;

import launcher.Launcher;
import launcher.helper.IOHelper;
import launcher.helper.LogHelper;
import launcher.helper.SecurityHelper;
import launcher.helper.VerifyHelper;
import launcher.request.Request.Type;
import launcher.request.RequestException;
import launcher.serialize.HInput;
import launcher.serialize.HOutput;
import launchserver.LaunchServer;
import launchserver.response.auth.AuthResponse;
import launchserver.response.auth.CheckServerResponse;
import launchserver.response.auth.JoinServerResponse;
import launchserver.response.profile.BatchProfileByUsernameResponse;
import launchserver.response.profile.ProfileByUUIDResponse;
import launchserver.response.profile.ProfileByUsernameResponse;
import launchserver.response.update.LauncherResponse;
import launchserver.response.update.UpdateListResponse;
import launchserver.response.update.UpdateResponse;

import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;

public final class ResponseThread implements Runnable
{
    private final LaunchServer server;
    private final long id;
    private final Socket socket;

    public ResponseThread(LaunchServer server, long id, Socket socket) throws SocketException
    {
        this.server = server;
        this.id = id;
        this.socket = socket;

        // Fix socket flags
        IOHelper.setSocketFlags(socket);
    }

    @Override
    public void run()
    {
        if (!server.serverSocketHandler.logConnections)
        {
            LogHelper.debug("Connection #%d from %s", id, IOHelper.getIP(socket.getRemoteSocketAddress()));
        }

        // Process connection
        boolean cancelled = false;
        Throwable savedError = null;
        try (HInput input = new HInput(socket.getInputStream());
             HOutput output = new HOutput(socket.getOutputStream()))
        {
            Type type = readHandshake(input, output);
            if (type == null)
            { // Not accepted
                cancelled = true;
                return;
            }

            // Start response
            try
            {
                respond(type, input, output);
            }
            catch (RequestException e)
            {
                LogHelper.subDebug(String.format("#%d Request error: %s", id, e.getMessage()));
                output.writeString(e.getMessage(), 0);
            }
        }
        catch (Throwable exc)
        {
            savedError = exc;
            LogHelper.error(exc);
        }
        finally
        {
            IOHelper.close(socket);
            if (!cancelled)
            {
                server.serverSocketHandler.onDisconnect(id, savedError);
            }
        }
    }

    private Type readHandshake(HInput input, HOutput output) throws IOException
    {
        boolean legacy = false;

        // Verify magic number
        int magicNumber = input.readInt();
        if (magicNumber != Launcher.PROTOCOL_MAGIC)
        {
            if (magicNumber != Launcher.PROTOCOL_MAGIC - 1)
            { // Previous launcher protocol
                output.writeBoolean(false);
                throw new IOException(String.format("#%d Protocol magic mismatch", id));
            }
            legacy = true;
        }

        // Verify key modulus
        BigInteger keyModulus = input.readBigInteger(SecurityHelper.RSA_KEY_LENGTH + 1);
        if (!keyModulus.equals(server.privateKey.getModulus()))
        {
            output.writeBoolean(false);
            throw new IOException(String.format("#%d Key modulus mismatch", id));
        }

        // Read request type
        Type type = Type.read(input);
        if (legacy && type != Type.LAUNCHER)
        {
            output.writeBoolean(false);
            throw new IOException(String.format("#%d Not LAUNCHER request on legacy protocol", id));
        }
        if (!server.serverSocketHandler.onHandshake(id, type))
        {
            output.writeBoolean(false);
            return null;
        }

        // Protocol successfully verified
        output.writeBoolean(true);
        output.flush();
        return type;
    }

    private void respond(Type type, HInput input, HOutput output) throws Throwable
    {
        if (server.serverSocketHandler.logConnections)
        {
            LogHelper.info("Connection #%d from %s: %s", id, IOHelper.getIP(socket.getRemoteSocketAddress()), type.name());
        }
        else
        {
            LogHelper.subDebug("#%d Type: %s", id, type.name());
        }

        // Choose response based on type
        Response response;
        switch (type)
        {
            case PING:
                response = new PingResponse(server, id, input, output);
                break;
            case AUTH:
                response = new AuthResponse(server, id, input, output, IOHelper.getIP(socket.getRemoteSocketAddress()));
                break;
            case JOIN_SERVER:
                response = new JoinServerResponse(server, id, input, output);
                break;
            case CHECK_SERVER:
                response = new CheckServerResponse(server, id, input, output);
                break;
            case LAUNCHER:
                response = new LauncherResponse(server, id, input, output);
                break;
            case UPDATE:
                response = new UpdateResponse(server, id, input, output);
                break;
            case UPDATE_LIST:
                response = new UpdateListResponse(server, id, input, output);
                break;
            case PROFILE_BY_USERNAME:
                response = new ProfileByUsernameResponse(server, id, input, output);
                break;
            case PROFILE_BY_UUID:
                response = new ProfileByUUIDResponse(server, id, input, output);
                break;
            case BATCH_PROFILE_BY_USERNAME:
                response = new BatchProfileByUsernameResponse(server, id, input, output);
                break;
            case CUSTOM:
                String name = VerifyHelper.verifyIDName(input.readASCII(255));
                response = server.serverSocketHandler.newCustomResponse(name, id, input, output);
                break;
            default:
                throw new AssertionError("Unsupported request type: " + type.name());
        }

        // Reply
        response.reply();
        LogHelper.subDebug("#%d Replied", id);
    }
}