package protocolP2P; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import exception.ProtocolError; import exception.InternalError; import exception.SizeError; import exception.TransmissionError; import tools.BytesArrayTools; import java.util.Arrays; import java.io.UnsupportedEncodingException; /** Representation of payload for load response. * @author Louis Royer * @author Flavien Haas * @author JS Auge * @version 1.0 */ public class FilePart extends Payload { private String filename; private long totalSize; private long offset; private byte[] partialContent; /** Constructor (typically used by server) with informations about file part to send as parameters. * @param filename name of the file to send * @param totalSize total size of the file to send * @param offset where in the file begins the part we are sending * @param partialContent content of the file we send * @throws InternalError */ public FilePart(String filename, long totalSize, long offset, byte[] partialContent) throws InternalError { super(RequestResponseCode.LOAD_RESPONSE); /* asserts to help debugging */ assert totalSize >= 0 : "totalSize cannot be negative"; assert partialContent.length != 0 : "partialContent.length cannot be zero"; assert totalSize >= partialContent.length : "totalSize must be greater than partialContent.length"; assert offset >= 0 : "offset cannot be negative"; assert filename != null : "filename is required"; if (totalSize < 0 || partialContent.length == 0 || totalSize < partialContent.length || offset < 0 || filename == null) { throw new InternalError(); } this.filename = filename; this.totalSize = totalSize; this.offset = offset; this.partialContent = partialContent; } /** Constructor (typically used by client) with datagram received as parameter. * @param datagram the full datagram received * @throws SizeError * @throws InternalError * @throws TransmissionError */ protected FilePart(byte[] datagram) throws TransmissionError, SizeError, ProtocolError, InternalError { super(datagram); /* assert to help debugging */ assert requestResponseCode == RequestResponseCode.LOAD_RESPONSE : "FilePart subclass is incompatible with this datagram, request/response code must be checked before using this constructor"; /* InternalErrorException */ if (requestResponseCode != RequestResponseCode.LOAD_RESPONSE) { throw new InternalError(); } setOffset(datagram); // this can throw SizeError setTotalSize(datagram); // this can throw SizeError setFilename(datagram); // this can throw ProtocolError, SizeError setPartialContent(datagram); // this can throw SizeError } /** Returns a byte[] containing datagram with padding. * This datagram is still incomplete and should not be send directly. * ProtocolP2PDatagram will use this method to generate the complete datagram. * @return datagram with padding * @throws InternalError */ protected byte[] toDatagram() throws InternalError { // compute payload size int size = 28 + filename.length() + partialContent.length; byte[] datagram = new byte[size]; // java initialize all to zero // set request/response code datagram[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue; // bits 16-31 are reserved for future use setPayloadSize(size - 8, datagram); // write offset to datagram (Byte 8) BytesArrayTools.write(datagram, 8, offset); // write totalSize to datagram (Byte 16) BytesArrayTools.write(datagram, 16, totalSize); // write filename’s size to datagram BytesArrayTools.write(datagram, 24, filename.length()); // write filename to datagram try { byte[] bFilename = filename.getBytes("UTF-8"); int i = filename.length() + 24; for (byte b : bFilename) { datagram[i] = b; i += 1; } // write partialContent to datagram for (byte b: partialContent) { datagram[i] = b; i += 1; } return datagram; } catch (UnsupportedEncodingException e) { throw new InternalError(); } } /** Write from bytes 8 to 15 of datagram into offset. * @param datagram received datagram * @throws SizeError */ private void setOffset(byte[] datagram) throws SizeError { offset = BytesArrayTools.readLong(datagram, 8); } /** Write from bytes 16 to 23 of datagram into totalSize. * @param datagram received datagram * @throws SizeError */ private void setTotalSize(byte[] datagram) throws SizeError { totalSize = BytesArrayTools.readLong(datagram, 16); } /** Read filename’s size from bytes 24 to 27 of datagram. * @param datagram received datagram * @throws ProtocolError * @throws SizeError * @return filename’s size */ private int getFilenameSize(byte[] datagram) throws SizeError, ProtocolError { int size = BytesArrayTools.readInt(datagram, 24); // this can throw SizeError // filename size cannot be zero if (size == 0) { throw new ProtocolError(); } // offset (8B) + totalSize (8B) + filenameSize (4B) = 20B if ((20 + size) > getPayloadSize(datagram)) { throw new ProtocolError(); } return size; } /** Write filename from byte 28 to byte (28 + (filenameSize - 1)) of datagram. * @param datagram received datagram * @throws ProtocolError * @throws SizeError * @throws InternalError */ private void setFilename(byte[] datagram) throws ProtocolError, SizeError, InternalError { int filenameSize = getFilenameSize(datagram); // this can throw ProtocolError or SizeError try { filename = new String(datagram, 28, filenameSize, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new InternalError(); } } /** Write partialContent from byte (28 + filenameSize) to byte (8 + payloadSize) of datagram. * @param datagram received datagram * @throws SizeError * @throws ProtocolError */ private void setPartialContent(byte[] datagram) throws ProtocolError, SizeError { int start = 28 + getFilenameSize(datagram); // this can throw SizeError or ProtocolError int end = 8 + getPayloadSize(datagram); // this can throw SizeError try { partialContent = Arrays.copyOfRange(datagram, start, end); } catch (ArrayIndexOutOfBoundsException e) { throw new ProtocolError(); } } /** partialContent getter. * @return partialcontent */ public byte[] getPartialContent() { return partialContent; } /** filename getter. * @return String */ public String getFilename() { return filename; } /** offset getter. * @return offset */ public long getOffset() { return offset; } /** totalSize getter. * @return totalSize */ public long getTotalSize() { return totalSize; } }