com.youTransactor.uCube.rpc.RPCManager.java Source code

Java tutorial

Introduction

Here is the source code for com.youTransactor.uCube.rpc.RPCManager.java

Source

/**
 * Copyright (C) 2011-2016, YouTransactor. All Rights Reserved.
 *
 * Use of this product is contingent on the existence of an executed license
 * agreement between YouTransactor or one of its sublicensee, and your
 * organization, which specifies this software's terms of use. This software
 * is here defined as YouTransactor Intellectual Property for the purposes
 * of determining terms of use as defined within the license agreement.
 */
package com.youTransactor.uCube.rpc;

import com.youTransactor.uCube.LogManager;
import com.youTransactor.uCube.Tools;
import com.youTransactor.uCube.rpc.command.EnterSecureSessionCommand;

import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author gbillard on 3/11/16.
 */
public class RPCManager {

    private IConnexionManager connexionManager;
    private OutputStream out;
    private boolean secureSession = false;
    private byte sequenceNumber = 0;
    private Map<Short, IRPCMessageHandler> messageHandlerByCommandId = new HashMap<>();

    private RPCManager() {
    }

    public synchronized void sendCommand(RPCCommand command) {
        if (out == null) {
            if (connexionManager == null || !connexionManager.connect()) {
                LogManager.debug(RPCManager.class.getSimpleName(), "unable to connect to device");

                command.setState(RPCCommandStatus.CONNECT_ERROR);

                return;
            }
        }

        LogManager.debug(RPCManager.class.getSimpleName(),
                "send command ID: 0x" + Integer.toHexString(command.getCommandId()));
        LogManager.debug(RPCManager.class.getSimpleName(),
                "send command data: 0x" + Tools.bytesToHex(command.getPayload()));

        messageHandlerByCommandId.put(command.getCommandId(), command);

        command.setState(RPCCommandStatus.SENDING);

        try {
            /* reset sequence number before entering in secure session */
            if (command.getCommandId() == Constants.ENTER_SECURED_SESSION) {
                sequenceNumber = 0;
            }

            if (secureSession && (command.getCommandId() != Constants.EXIT_SECURED_SESSION)) {
                sendSecureCommand(command);
            } else {
                sendInsecureCommand(command);
            }

            out.flush();

            LogManager.debug(RPCManager.class.getSimpleName(),
                    "sent command ID: 0x" + Integer.toHexString(command.getCommandId()));

            command.setState(RPCCommandStatus.SENT);

        } catch (Exception e) {
            LogManager.debug(RPCManager.class.getName(),
                    "send command error for Id: " + Integer.toHexString(command.getCommandId()), e);

            messageHandlerByCommandId.remove(command.getCommandId());

            command.setState(RPCCommandStatus.FAILED);
        }
    }

    public void start(InputStream in, OutputStream out) {
        this.out = out;

        DAEMON = new RPCDaemon(in);

        new Thread(DAEMON).start();

        if (secureSession) {
            new EnterSecureSessionCommand().execute(null);
        }
    }

    public void stop() {
        if (DAEMON != null) {
            DAEMON.stop();
        }

        out = null;

        for (IRPCMessageHandler handler : messageHandlerByCommandId.values()) {
            handler.processMessage(null);
        }

        messageHandlerByCommandId.clear();
    }

    public boolean isReady() {
        return out != null;
    }

    public boolean connect() {
        return connexionManager != null && connexionManager.connect();
    }

    public void setConnexionManager(IConnexionManager connexionManager) {
        this.connexionManager = connexionManager;
    }

    private void sendSecureCommand(RPCCommand command) throws IOException {
        byte[] payload = command.getPayload();

        if (payload.length == 0) {
            sendInsecureCommand(command);
            return;
        }

        byte[] message = new byte[payload.length + Constants.RPC_SECURED_HEADER_LEN + Constants.RPC_SRED_MAC_SIZE];
        int securedLen = payload.length + Constants.RPC_SECURED_HEADER_CRYPTO_RND_LEN + Constants.RPC_SRED_MAC_SIZE;

        int offset = 0;

        message[offset++] = (byte) (securedLen / 0x100);
        message[offset++] = (byte) (securedLen % 0x100);
        message[offset++] = sequenceNumber++;
        message[offset++] = (byte) (command.getCommandId() / 0x100);
        message[offset++] = (byte) (command.getCommandId() % 0x100);

        message[offset++] = (byte) 0x7F; // TODO should be random

        System.arraycopy(payload, 0, message, offset, payload.length);
        offset += payload.length;

        /* Padding the last 4 bytes with 0x00 (SRED OPT) */
        for (int i = 0; i < Constants.RPC_SRED_MAC_SIZE; i++) {
            message[offset++] = 0x00;
        }

        int crc = computeChecksumCRC16(message);

        out.write(Constants.STX);
        IOUtils.write(message, out);
        out.write((byte) (crc / 0x100));
        out.write((byte) (crc % 0x100));
        out.write(Constants.ETX);

        LogManager.debug(RPCManager.class.getSimpleName(), "sent secure message: 0x" + Tools.bytesToHex(message));
    }

    private void sendInsecureCommand(RPCCommand command) throws IOException {
        byte[] payload = command.getPayload();

        byte[] message = new byte[payload.length + Constants.RPC_HEADER_LEN];
        int offset = 0;

        message[offset++] = (byte) (payload.length / 0x100);
        message[offset++] = (byte) (payload.length % 0x100);
        message[offset++] = sequenceNumber++;
        message[offset++] = (byte) (command.getCommandId() / 0x100);
        message[offset++] = (byte) (command.getCommandId() % 0x100);

        System.arraycopy(payload, 0, message, offset, payload.length);

        int crc = computeChecksumCRC16(message);

        out.write(Constants.STX);
        IOUtils.write(message, out);
        out.write((byte) (crc / 0x100));
        out.write((byte) (crc % 0x100));
        out.write(Constants.ETX);

        LogManager.debug(RPCManager.class.getSimpleName(), "sent message: 0x" + Tools.bytesToHex(message));
    }

    private void processMessage(RPCMessage message) {
        LogManager.debug(RPCManager.class.getSimpleName(),
                "received command ID: 0x" + Integer.toHexString(message.getCommandId()));
        LogManager.debug(RPCManager.class.getSimpleName(),
                "received command Status: 0x" + Integer.toHexString(message.getStatus()));
        LogManager.debug(RPCManager.class.getSimpleName(),
                "received command data: 0x" + Tools.bytesToHex(message.getData()));

        if (message.getStatus() == Constants.SUCCESS_STATUS) {
            if (message.getCommandId() == Constants.ENTER_SECURED_SESSION) {
                /* Set Secure Session State*/
                secureSession = true;
            } else if (message.getCommandId() == Constants.EXIT_SECURED_SESSION) {
                secureSession = false;
            }
        }

        IRPCMessageHandler handler = messageHandlerByCommandId.remove(message.getCommandId());

        if (handler == null && messageHandlerByCommandId.size() == 1) {
            /* command ID is 0x8000 in case of error => unable to recognize command */
            handler = messageHandlerByCommandId.values().iterator().next();
            messageHandlerByCommandId.clear();
        }

        if (handler != null) {
            try {
                handler.processMessage(message);
            } catch (Exception e) {
                LogManager.debug(RPCManager.class.getSimpleName(), "RPC command response process error", e);
            }
        }
    }

    public static RPCManager getInstance() {
        return INSTANCE;
    }

    /**
     * this function to calculate the checksum 16bit
     *
     * @param bytes the payload data
     * @return the calculated CRC16
     */
    private static int computeChecksumCRC16(byte bytes[]) {
        int crc = 0x0000;
        int temp;
        int crc_byte;

        for (int byte_index = 0; byte_index < bytes.length; byte_index++) {

            crc_byte = bytes[byte_index];

            if (crc_byte < 0)
                crc_byte += 256;

            for (int bit_index = 0; bit_index < 8; bit_index++) {

                temp = (crc >> 15) ^ (crc_byte >> 7);

                crc <<= 1;
                crc &= 0xFFFF;

                if (temp > 0) {
                    crc ^= 0x1021;
                    crc &= 0xFFFF;
                }

                crc_byte <<= 1;
                crc_byte &= 0xFF;

            }
        }

        return crc;
    }

    /**
     * The size of the biggest packet is the LOAD command and its 2052
     * 2040(block) + 2(length) + 1(isLastBlock) + 2(command_ID) + 7(RPC Headers ETX,STX,SEQ,CRC,PLL) = 2052
     */
    public static final int MAX_RPC_PACKET_SIZE = 2068;

    private static final RPCManager INSTANCE = new RPCManager();
    private static RPCDaemon DAEMON;

    private static class RPCDaemon implements Runnable {

        private InputStream in;
        private boolean interrupted = false;

        private RPCDaemon(InputStream in) {
            this.in = in;
        }

        private void stop() {
            interrupted = true;
        }

        @Override
        public void run() {
            byte[] bufferFromStream = new byte[MAX_RPC_PACKET_SIZE];
            byte[] bufferToDeliver = new byte[MAX_RPC_PACKET_SIZE];

            int nb_bytes;
            boolean isPacketComplete = false;
            boolean waiting_for_first_packet = true;
            short expected_length = 0;
            short bufferToDeliverOffset = 0;

            while (!interrupted) {
                try {
                    nb_bytes = in.read(bufferFromStream);

                    if (nb_bytes > 0) {
                        if (waiting_for_first_packet) {
                            if (bufferFromStream[0] != Constants.STX) {
                                isPacketComplete = false;
                                waiting_for_first_packet = true;

                                continue;
                            }

                            expected_length = Tools.makeShort(bufferFromStream[1], bufferFromStream[2]);
                            expected_length += 1 + 2 + 1 + 2 + 3; /* ETX,CRC,STX,CMDID,LENGTH */

                            if (expected_length > MAX_RPC_PACKET_SIZE) {
                                isPacketComplete = false;
                                waiting_for_first_packet = true;

                            } else {
                                waiting_for_first_packet = false;
                            }

                            bufferToDeliverOffset = 0;
                        }

                        System.arraycopy(bufferFromStream, 0, bufferToDeliver, bufferToDeliverOffset, nb_bytes);
                        bufferToDeliverOffset += nb_bytes;
                    }

                    if (bufferToDeliverOffset == expected_length) {
                        if (bufferToDeliver[expected_length - 1] != Constants.ETX) {
                            continue;
                        }

                        isPacketComplete = true;
                        bufferFromStream = new byte[MAX_RPC_PACKET_SIZE];
                    }

                    if (isPacketComplete) {
                        waiting_for_first_packet = true;
                        isPacketComplete = false;

                        LogManager.debug(RPCManager.class.getSimpleName(),
                                "received: " + Tools.bytesToHex(bufferToDeliver));

                        RPCMessage response = new RPCMessage(bufferToDeliver, INSTANCE.secureSession);

                        /* Reset the buffer to the next message to avoid overwriting problems */
                        Arrays.fill(bufferToDeliver, (byte) 0);

                        INSTANCE.processMessage(response);
                    }

                } catch (Exception e) {
                    if (!interrupted) {
                        if (e instanceof IOException) {
                            LogManager.debug(RPCManager.class.getName(), "socket closed");
                        } else {
                            LogManager.debug(RPCManager.class.getName(), "RPC socket read  error", e);
                        }

                        INSTANCE.stop();
                    }
                }
            }

            DAEMON = null;
        }
    }

}