com.github.mrstampy.gameboot.otp.netty.client.ClientHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.github.mrstampy.gameboot.otp.netty.client.ClientHandler.java

Source

/*
 *              ______                        ____              __ 
 *             / ____/___ _____ ___  ___     / __ )____  ____  / /_
 *            / / __/ __ `/ __ `__ \/ _ \   / __  / __ \/ __ \/ __/
 *           / /_/ / /_/ / / / / / /  __/  / /_/ / /_/ / /_/ / /_  
 *           \____/\__,_/_/ /_/ /_/\___/  /_____/\____/\____/\__/  
 *                                                 
 *                                 .-'\
 *                              .-'  `/\
 *                           .-'      `/\
 *                           \         `/\
 *                            \         `/\
 *                             \    _-   `/\       _.--.
 *                              \    _-   `/`-..--\     )
 *                               \    _-   `,','  /    ,')
 *                                `-_   -   ` -- ~   ,','
 *                                 `-              ,','
 *                                  \,--.    ____==-~
 *                                   \   \_-~\
 *                                    `_-~_.-'
 *                                     \-~
 * 
 *                       http://mrstampy.github.io/gameboot/
 *
 * Copyright (C) 2015, 2016 Burton Alexander
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * 
 */
package com.github.mrstampy.gameboot.otp.netty.client;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import javax.resource.spi.IllegalStateException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mrstampy.gameboot.messages.Response;
import com.github.mrstampy.gameboot.messages.Response.ResponseCode;
import com.github.mrstampy.gameboot.otp.OneTimePad;
import com.github.mrstampy.gameboot.otp.messages.OtpKeyRequest;

import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Future;

/**
 * The Class ClientHandler.
 */
@Sharable
public class ClientHandler extends ChannelDuplexHandler {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private OneTimePad pad;

    private byte[] otpKey;

    private Channel clearChannel;

    private Long systemId;

    private Response lastResponse;

    private CountDownLatch responseLatch;

    /*
     * (non-Javadoc)
     * 
     * @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.
     * channel.ChannelHandlerContext)
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SslHandler handler = ctx.pipeline().get(SslHandler.class);

        if (handler == null)
            return;

        handler.handshakeFuture().addListener(f -> validate(f, ctx));
    }

    private void validate(Future<? super Channel> f, ChannelHandlerContext ctx) {
        if (f.isSuccess()) {
            log.debug("Handshake successful with {}", ctx.channel());
        } else {
            log.error("Handshake unsuccessful, disconnecting {}", ctx.channel(), f.cause());
            ctx.close();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.
     * ChannelHandlerContext, java.lang.Object)
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object o) throws Exception {
        try {
            byte[] msg = (byte[]) o;

            if (isEncrypting(ctx)) {
                try {
                    decrypt(msg);
                    return;
                } catch (Exception e) {
                    log.error("Cannot decrypt: assuming delete request sent: {}", e.getMessage());
                }
            }

            unencrypted(ctx, msg);
        } finally {
            if (responseLatch != null)
                responseLatch.countDown();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see io.netty.channel.ChannelDuplexHandler#write(io.netty.channel.
     * ChannelHandlerContext, java.lang.Object, io.netty.channel.ChannelPromise)
     */
    @Override
    public void write(ChannelHandlerContext ctx, Object o, ChannelPromise promise) throws Exception {
        byte[] msg = (byte[]) o;

        msg = isEncrypting(ctx) ? pad.convert(otpKey, msg) : msg;

        ctx.write(msg, promise);
    }

    private boolean isEncrypting(ChannelHandlerContext ctx) {
        return otpKey != null && clearChannel == ctx.channel();
    }

    private void unencrypted(ChannelHandlerContext ctx, byte[] msg) throws Exception {
        Response r = getResponse(msg);
        lastResponse = r;

        boolean c = ctx.pipeline().get(SslHandler.class) != null;

        log.info("Unencrypted: on {} channel\n{}", (c ? "secured" : "unsecured"), mapper.writeValueAsString(r));

        if (!ok(r.getResponseCode()))
            return;

        if (ResponseCode.INFO == r.getResponseCode()) {
            Object[] payload = r.getPayload();
            if (payload == null || payload.length == 0 || !(payload[0] instanceof Map<?, ?>)) {
                throw new IllegalStateException("Expecting map of systemId:[value]");
            }

            systemId = (Long) ((Map<?, ?>) payload[0]).get("systemId");

            log.info("Setting system id {}", systemId);
            clearChannel = ctx.channel();
            return;
        }

        JsonNode node = mapper.readTree(msg);
        JsonNode response = node.get("payload");

        boolean hasKey = response != null && response.isArray() && response.size() == 1;

        if (hasKey) {
            log.info("Setting key");
            otpKey = response.get(0).binaryValue();
            return;
        }

        switch (r.getType()) {
        case OtpKeyRequest.TYPE:
            log.info("Deleting key");
            otpKey = null;
            break;
        default:
            break;
        }
    }

    private Response getResponse(byte[] msg) throws IOException, JsonParseException, JsonMappingException {
        return mapper.readValue(msg, Response.class);
    }

    private boolean ok(ResponseCode responseCode) {
        if (responseCode == null)
            return false;
        switch (responseCode) {
        case SUCCESS:
        case INFO:
            return true;
        default:
            return false;
        }
    }

    private void decrypt(byte[] msg) throws Exception {
        byte[] converted = pad.convert(otpKey, msg);

        Response r = getResponse(converted);
        lastResponse = r;

        log.info("Encrypted: \n{}", mapper.writeValueAsString(r));
    }

    /**
     * Gets the system id.
     *
     * @return the system id
     */
    public Long getSystemId() {
        return systemId;
    }

    /**
     * Gets the clear channel.
     *
     * @return the clear channel
     */
    public Channel getClearChannel() {
        return clearChannel;
    }

    /**
     * Gets the last response.
     *
     * @return the last response
     */
    public Response getLastResponse() {
        return lastResponse;
    }

    /**
     * Sets the response latch.
     *
     * @param responseLatch
     *          the new response latch
     */
    public void setResponseLatch(CountDownLatch responseLatch) {
        this.responseLatch = responseLatch;
    }

    /**
     * Checks for key.
     *
     * @return true, if successful
     */
    public boolean hasKey() {
        return otpKey != null;
    }

}