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