com.github.mrstampy.gameboot.otp.websocket.WebSocketEndpoint.java Source code

Java tutorial

Introduction

Here is the source code for com.github.mrstampy.gameboot.otp.websocket.WebSocketEndpoint.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.websocket;

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

import javax.resource.spi.IllegalStateException;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.OnMessage;
import javax.websocket.Session;

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;

/**
 * The Class WebSocketEndpoint.
 */
public class WebSocketEndpoint extends Endpoint {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private OneTimePad pad;

    private byte[] otpKey;

    private Session session;

    private Long systemId;

    private Response lastResponse;

    private CountDownLatch responseLatch;

    /*
     * (non-Javadoc)
     * 
     * @see javax.websocket.Endpoint#onOpen(javax.websocket.Session,
     * javax.websocket.EndpointConfig)
     */
    @Override
    public void onOpen(Session session, EndpointConfig config) {
        log.debug("Session {} open on channel", session.getId());

        session.addMessageHandler(new MessageHandler.Whole<byte[]>() {

            @Override
            public void onMessage(byte[] message) {
                try {
                    WebSocketEndpoint.this.onMessage(message, session);
                } catch (Exception e) {
                    log.error("Unexpected exception", e);
                }
            }
        });
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.websocket.Endpoint#onClose(javax.websocket.Session,
     * javax.websocket.CloseReason)
     */
    @Override
    public void onClose(Session session, CloseReason closeReason) {
        log.debug("Session {} closed on channel", session.getId());
        if (this.session == session)
            this.session = null;
    }

    /**
     * On message.
     *
     * @param msg
     *          the msg
     * @param session
     *          the session
     * @throws Exception
     *           the exception
     */
    @OnMessage
    public void onMessage(byte[] msg, Session session) throws Exception {
        log.debug("Message received for channel {}", session.getId());
        try {
            if (isEncrypting(session)) {
                try {
                    decrypt(msg);
                    return;
                } catch (Exception e) {
                    log.error("Cannot decrypt: assuming delete request sent: {}", e.getMessage());
                }
            }

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

    /**
     * Send message.
     *
     * @param msg
     *          the msg
     * @param session
     *          the session
     * @throws Exception
     *           the exception
     */
    public void sendMessage(byte[] msg, Session session) throws Exception {
        byte[] converted = isEncrypting(session) ? pad.convert(otpKey, msg) : msg;

        session.getBasicRemote().sendBinary(ByteBuffer.wrap(converted));
    }

    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));
    }

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

        log.info("Unencrypted: on session {}\n{}", ctx.getId(), 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);
            this.session = ctx;
            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 boolean isEncrypting(Session ctx) {
        return otpKey != null && this.session == ctx;
    }

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

    /**
     * Gets the session.
     *
     * @return the session
     */
    public Session getSession() {
        return session;
    }

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

    /**
     * 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;
    }

}