rn.beleg.protocol.Protocol.java Source code

Java tutorial

Introduction

Here is the source code for rn.beleg.protocol.Protocol.java

Source

/*
 * Copyright (c) 2004-2014 Frank Kubis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package rn.beleg.protocol;

import org.apache.commons.io.FilenameUtils;
import rn.beleg.protocol.model.DataPackage;
import rn.beleg.protocol.model.FirstPackage;
import rn.beleg.protocol.model.ReceivePackage;
import rn.beleg.protocol.model.exception.NoStartPackageException;
import rn.beleg.utility.ByteUtilities;
import rn.beleg.utility.CRC32;
import rn.beleg.utility.Output;

import javax.xml.crypto.Data;
import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
 * Implementation of IProtocol
 *
 * @author Frank Kubis <f.kubis@aranox.de>
 */
public class Protocol implements IProtocol {
    /**
     * The size of the whole package
     */
    public final static int PACKAGE_SIZE = 1472; // mtu - ip-header - udp-header (1500 - 20 - 8)

    /**
     * The size of the data part
     *
     * PACkAGE_SIZE - Session - Id
     */
    private final static int DATA_PACKAGE_SIZE = PACKAGE_SIZE - 2 - 1;

    /**
     * Util for standard output style
     */
    private static final Output output = new Output();

    /**
     * Map that contains a list of
     */
    private static Map<byte[], LinkedList<DataPackage>> dataPackages = new HashMap<byte[], LinkedList<DataPackage>>();

    /**
     * Map contains firstPackages by session
     */
    private static Map<byte[], FirstPackage> firstPackages = new HashMap<byte[], FirstPackage>();

    /**
     * The file that should encoded
     */
    private File file;

    /**
     * @return The file
     */
    public File getFile() {
        return file;
    }

    /**
     * Change the file
     *
     * @param file the new file
     */
    public void setFile(File file) {
        this.file = file;
    }

    /**
     * Encode the file
     *
     * @return Return an iterable byte[] could sent
     */
    public Iterable<byte[]> encode() {
        LinkedList<byte[]> list = new LinkedList<byte[]>();
        byte[] data = new byte[(int) file.length()];
        byte[] session = ByteUtilities.random(2);

        try {
            FileInputStream in = new FileInputStream(this.file);
            //noinspection ResultOfMethodCallIgnored
            in.read(data);
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            output.error("File " + file.getAbsolutePath() + " not found.");
            System.exit(1);
        } catch (IOException e) {
            output.error(e.toString());
            System.exit(1);
        } finally {
            // Create first package
            FirstPackage firstPackage = null;
            try {
                firstPackage = new FirstPackage(session, 0, "Start".getBytes("ASCII"), file.length(),
                        file.getName().length(), file.getName());

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                System.exit(1);
            }

            output.info(String.format("Session        %s", ByteUtilities.byteToHex(session)));
            output.info(String.format("FileNameLength %s", firstPackage.getFileNameLength()));
            output.info(String.format("Filename       %s", firstPackage.getFileName()));
            output.info(String.format("FileLength     %s", firstPackage.getFileLength()));

            list.add(firstPackage.serialize());

            // Create data packages
            int step = 1;
            for (int start = 0; start < data.length; start += DATA_PACKAGE_SIZE) {
                int end = start + DATA_PACKAGE_SIZE;
                int packageId = step % 2;
                DataPackage dataPackage;
                if (end < data.length) {
                    // normal package
                    byte[] packageData = Arrays.copyOfRange(data, start, end);
                    dataPackage = new DataPackage(session, packageId, packageData);
                } else {
                    byte[] packageData = Arrays.copyOfRange(data, start, data.length);
                    dataPackage = new DataPackage(session, packageId, packageData, CRC32.calc(data));
                    // last package got crc, check length with crc
                    if (dataPackage.packageSize() >= DATA_PACKAGE_SIZE) {
                        // package to big, split again
                        byte[] tmp = Arrays.copyOfRange(packageData, 0, packageData.length / 2);
                        DataPackage dtmp = new DataPackage(session, packageId, tmp);
                        list.add(dtmp.serialize());

                        tmp = Arrays.copyOfRange(packageData, packageData.length / 2, packageData.length);
                        dataPackage = new DataPackage(session, packageId, tmp, CRC32.calc(data));
                    }
                }
                output.info(String.format("Data part package size %s", dataPackage.getData().length));
                list.add(dataPackage.serialize());
                step++;
            }
        }

        return list;
    }

    /**
     * Decode a received byte[] and creates an reply
     *
     * @param received the received byte[]
     *
     * @return the reply
     */
    public byte[] decode(byte[] received) {
        byte[] session = this.receivedToSession(received);
        LinkedList<DataPackage> list;
        ReceivePackage receivePackage;
        output.info("Getting package with length " + received.length);

        output.info(String.format("Session %s", ByteUtilities.byteToHex(session)));

        // Create lists with data packages
        if (this.firstPackagesContainsKey(session)) {
            FirstPackage firstPackage = getFirstPackage(session);
            if (firstPackages == null) {
                clean(session);
                output.error("No first package found for this session.");

                return new byte[0];
            }

            if (this.dataPackagesContainsKey(session)) {
                list = getDataPackages(session);
            } else {
                list = new LinkedList<DataPackage>();
                dataPackages.put(session, list);
            }
            DataPackage dataPackage;
            boolean isLast = false;
            int total = 0;
            for (DataPackage dp : list) {
                total += dp.getData().length;
            }
            if (list.size() > 0) {
                DataPackage lastPackage = list.getLast();
                // its the last package? If true, calc crc
                isLast = firstPackage.getFileLength() <= total + received.length - 3; // sessionid + packageid = 3 byte
                dataPackage = new DataPackage(received, isLast);

                if (lastPackage.getPackageId() == dataPackage.getPackageId()) {
                    output.error(
                            String.format("Getting two packages with id %s in a row", lastPackage.getPackageId()));

                    return new byte[0];
                }
            } else {
                dataPackage = new DataPackage(received);
            }
            total += dataPackage.getData().length;
            output.info(String.format("Total received %s of %s", total, firstPackage.getFileLength()));
            output.info(String.format("Data part size %s", dataPackage.getData().length));
            list.add(dataPackage);

            if (isLast) {
                output.info("Last package, try to create file");

                byte[] fileData = new byte[0];
                for (DataPackage p : list) {
                    fileData = ByteUtilities.appendByteArrays(fileData, p.getData());
                }

                // check CRC,
                if (!Arrays.equals(CRC32.calc(fileData), list.getLast().getCrc())) {
                    clean(session);
                    output.error("CRC does not match");

                    return new byte[0];
                }
                output.info("CRC ok");

                try {
                    String fileName = firstPackage.getFileName();
                    File file = new File(fileName);
                    String extension = FilenameUtils.getExtension(fileName);
                    String baseName = FilenameUtils.getBaseName(fileName);

                    int i = 1;
                    while (file.exists() && !file.isDirectory()) {
                        file = new File(baseName + i + "." + extension);
                        i++;
                    }
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    fileOutputStream.write(fileData);
                    fileOutputStream.close();
                    this.clean(session);

                    output.success("Write file to: " + file.getAbsolutePath());
                } catch (Exception e) {
                    output.error(e.getMessage());
                    System.exit(2);
                }
            }

            receivePackage = new ReceivePackage(session, ByteUtilities.intToByte(dataPackage.getPackageId()));
        } else {
            output.info("New session.");

            try {
                FirstPackage firstPackage = new FirstPackage(received);
                output.info("Received start package");
                output.info(String.format("Session        %s",
                        Integer.toHexString(ByteUtilities.byteArrayToInt(firstPackage.getSessionId()))));
                output.info(String.format("FileNameLength %s", firstPackage.getFileNameLength()));
                output.info(String.format("Filename       %s", firstPackage.getFileName()));
                output.info(String.format("FileLength     %s", firstPackage.getFileLength()));
                firstPackages.put(session, firstPackage);
                // First package -> new session -> no other handling required
                receivePackage = new ReceivePackage(session, ByteUtilities.intToByte(firstPackage.getPackageId()));
            } catch (NoStartPackageException e) {
                output.error("Not a start package");
                return new byte[0];
            }
        }

        return receivePackage.serialize();
    }

    /**
     * Get the session from the received byte[]
     *
     * @param received Get the session from by
     *
     * @return the session as byte[]
     */
    private byte[] receivedToSession(byte[] received) {
        return Arrays.copyOfRange(received, 0, 2);
    }

    /**
     * Check for containing session in firstPackages
     *
     * @param session the session
     *
     * @return true if found or false if not
     */
    private boolean firstPackagesContainsKey(byte[] session) {
        for (Map.Entry<byte[], FirstPackage> entry : firstPackages.entrySet())
            if (Arrays.equals(entry.getKey(), session))
                return true;

        return false;
    }

    /**
     * Check for containing session in dataPackage
     *
     * @param session the session
     *
     * @return true if found or false if not
     */
    private boolean dataPackagesContainsKey(byte[] session) {
        for (Map.Entry<byte[], LinkedList<DataPackage>> entry : dataPackages.entrySet())
            if (Arrays.equals(entry.getKey(), session))
                return true;

        return false;
    }

    /**
     * Get FirstPackage for session or null
     *
     * @param session the session
     *
     * @return the model or null
     */
    private FirstPackage getFirstPackage(byte[] session) {
        for (Map.Entry<byte[], FirstPackage> entry : firstPackages.entrySet())
            if (Arrays.equals(entry.getKey(), session))
                return entry.getValue();

        return null;
    }

    /**
     * Get List of dataPackages by session
     *
     * @param session the session
     *
     * @return List of dataPackages received until now or null
     */
    private LinkedList<DataPackage> getDataPackages(byte[] session) {
        for (Map.Entry<byte[], LinkedList<DataPackage>> entry : dataPackages.entrySet())
            if (Arrays.equals(entry.getKey(), session))
                return entry.getValue();

        return null;
    }

    /**
     * Remove the session from all maps
     *
     * @param session they session that should removed
     */
    private void clean(byte[] session) {
        Map<byte[], FirstPackage> newFirstPackages = new HashMap<byte[], FirstPackage>();
        for (Map.Entry<byte[], FirstPackage> entry : firstPackages.entrySet())
            if (!Arrays.equals(entry.getKey(), session))
                newFirstPackages.put(entry.getKey(), entry.getValue());
        firstPackages = newFirstPackages;

        Map<byte[], LinkedList<DataPackage>> newDataPackages = new HashMap<byte[], LinkedList<DataPackage>>();
        for (Map.Entry<byte[], LinkedList<DataPackage>> entry : dataPackages.entrySet())
            if (!Arrays.equals(entry.getKey(), session))
                newDataPackages.put(entry.getKey(), entry.getValue());
        dataPackages = newDataPackages;
    }
}