package protocolP2P; import localException.InternalError; import localException.ProtocolError; import localException.SizeError; import localException.TransmissionError; import localException.VersionError; import localException.SocketClosed; import remoteException.NotATracker; import remoteException.EmptyDirectory; import remoteException.InternalRemoteError; import remoteException.NotFound; import remoteException.ProtocolRemoteError; import remoteException.VersionRemoteError; import remoteException.EmptyFile; import remoteException.UnknownHost; import tools.BytesArrayTools; import tools.HostItem; import protocolP2P.Payload; import protocolP2P.RequestResponseCode; import protocolP2P.LoadRequest; import protocolP2P.FileList; import protocolP2P.FilePart; import protocolP2P.RatioRequest; import protocolP2P.RatioResponse; import protocolP2P.UpdateRatio; import protocolP2P.Denied; import protocolP2P.SizeRequest; import protocolP2P.SizeResponse; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; import java.io.IOException; import java.util.Arrays; /** Representation of packet. * @author Louis Royer * @author Flavien Haas * @author JS Auge * @version 1.0 */ public class ProtocolP2PPacketUDP < T extends Payload> extends ProtocolP2PPacket < T > { private final static int CHECKSUM_POSITION = 2; private HostItem remoteHost; private SocketAddress responseSocketAddress; // socket address used when receptionning request and to sending response private DatagramSocket responseSocket; // socket used to recept request and send response private DatagramSocket requestSocket; // socket used to send request and to reception response /** Constructor with payload parameter (typically used when sending packet). * @param payload the payload associated with the packet to send */ public ProtocolP2PPacketUDP(T payload) { super(payload); } /** Send a Packet. Socket must be set and connected. * @param socket DatagramSocket used to send Packet. * @throws InternalError * @throws IOException */ protected void send(DatagramSocket socket) throws InternalError, IOException { send(socket, null); } /** Send a Packet. Socket must be set and connected, or an addr can be given. * @param socket DatagramSocket used to send Packet. * @param addr SocketAddress used to send Packet. * @throws InternalError * @throws IOException */ protected void send(DatagramSocket socket, SocketAddress addr) throws InternalError, IOException { assert socket != null : "Trying to send a Packet but no socket defined"; assert socket.isConnected() || addr != null : "Trying to send a Packet but socket not connected, and no socketAddress given"; if (socket == null || ((!socket.isConnected()) && (addr == null))) { throw new InternalError(); } // generate DatagramPacket byte[] packet = toPacket(); DatagramPacket datagramPacket = new DatagramPacket(packet, packet.length); if (addr != null) { datagramPacket.setSocketAddress(addr); } // send it socket.send(datagramPacket); } /** Send a Request throught socket. Socket must be connected (typically used from client). * @param socket DatagramSocket. Must be connected. * @throws InternalError * @throws IOException */ public void sendRequest(Object socket) throws InternalError, IOException { assert socket instanceof DatagramSocket: "Wrong socket type"; if (socket instanceof DatagramSocket) { requestSocket = (DatagramSocket)socket; send(requestSocket); } else { throw new InternalError(); } } /** Receive Request (typically used from server). * @param socket socket used to receive request * @throws TransmissionError * @throws ProtocolError * @throws VersionError * @throws InternalError * @throws SizeError * @throws IOException * @throws SocketClosed * @return ProtocolP2PPacket received. */ public ProtocolP2PPacketUDP(Object socket) throws TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, SocketClosed { super(socket); assert socket instanceof DatagramSocket : "Wrong socket type"; if (!(socket instanceof DatagramSocket)) { throw new InternalError(); } DatagramSocket ss = (DatagramSocket)socket; byte[] packetTmp = new byte[0xFFFF]; DatagramPacket reception = new DatagramPacket(packetTmp, packetTmp.length); ss.receive(reception); int payloadSize = Payload.getPayloadSize(packetTmp); byte[] packet = Arrays.copyOf(packetTmp, Payload.PAYLOAD_START_POSITION + payloadSize); responseSocketAddress = reception.getSocketAddress(); remoteHost = new HostItem(reception.getAddress().getHostName(), reception.getPort()); // contruction boolean protocolError = false; try { constructPacket(packet, ss); Payload payload = getPayload(); switch (payload.getRequestResponseCode()) { case PROTOCOL_ERROR : // we do not want to create an infinite loop of protocolError message exchange. protocolError = true; break; case VERSION_ERROR : case INTERNAL_ERROR : case EMPTY_DIRECTORY : case NOT_FOUND : case EMPTY_FILE: case LOAD_RESPONSE: case LIST_RESPONSE: case HASH_RESPONSE: case DISCOVER_RESPONSE: case NOT_A_TRACKER: case RATIO_RESPONSE: case DENIED: case SIZE_RESPONSE: // we were expecting a request, but we are receiving a response throw new ProtocolError(); default : break; } } catch (TransmissionError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); throw e; } catch (ProtocolError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(ss, responseSocketAddress); throw e; } catch (VersionError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.VERSION_ERROR))).send(ss, responseSocketAddress); throw e; } catch (InternalError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); throw e; } catch (SizeError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress); throw e; } if (protocolError) { throw new ProtocolError(); } } /** Send a Response to a Request (typically used from server). * @param response Packet to send as a response. * @throws InternalError * @throws IOException */ public >void sendResponse(U response) throws InternalError, IOException { assert response instanceof ProtocolP2PPacketUDP: "Wrong Packet type"; if (response instanceof ProtocolP2PPacketUDP) { ProtocolP2PPacketUDP r = (ProtocolP2PPacketUDP) response; assert responseSocket != null : "Cannot send response to a packet not received"; if (responseSocket == null) { throw new InternalError(); } assert responseSocketAddress != null : "Cannot send response because address of client has not been saved"; if (responseSocketAddress == null) { throw new InternalError(); } r.send(responseSocket, responseSocketAddress); } else { throw new InternalError(); } } /** Receive response (typically used by client). * @return ProtocolP2PPacket received * @throws EmptyFile * @throws NotFound * @throws NotATracker * @throws EmptyDirectory * @throws InternalRemoteError * @throws VersionRemoteError * @throws ProtocolRemoteError * @throws TransmissionError * @throws ProtocolError * @throws VersionError * @throws InternalError * @throws SizeError * @throws IOException * @throws UnknownHost */ public ProtocolP2PPacket receiveResponse() throws EmptyFile, NotFound, NotATracker, EmptyDirectory, InternalRemoteError, VersionRemoteError, ProtocolRemoteError, TransmissionError, ProtocolError, VersionError, InternalError, SizeError, IOException, UnknownHost { assert requestSocket != null : "Cannot receive response because request packet not sent."; if (requestSocket == null) { throw new InternalError(); } // reception byte[] packetTmp = new byte[0xFFFF]; DatagramPacket reception = new DatagramPacket(packetTmp, packetTmp.length); requestSocket.receive(reception); int payloadSize = Payload.getPayloadSize(packetTmp); byte[] packet = Arrays.copyOf(packetTmp, Payload.PAYLOAD_START_POSITION + payloadSize); // contruction try { ProtocolP2PPacketUDP p = new ProtocolP2PPacketUDP<>(packet); Payload payload = p.getPayload(); switch (payload.getRequestResponseCode()) { case PROTOCOL_ERROR : throw new ProtocolRemoteError(); case VERSION_ERROR : throw new VersionRemoteError(); case INTERNAL_ERROR : throw new InternalRemoteError(); case EMPTY_DIRECTORY : throw new EmptyDirectory(); case NOT_FOUND : throw new NotFound(); case EMPTY_FILE: throw new EmptyFile(); case NOT_A_TRACKER: throw new NotATracker(); case UNKNOWN_HOST: throw new UnknownHost(); default : return (ProtocolP2PPacket)p; } } catch (TransmissionError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } catch (ProtocolError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(requestSocket); throw e; } catch (VersionError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.VERSION_ERROR))).send(requestSocket); throw e; } catch (InternalError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } catch (SizeError e) { (new ProtocolP2PPacketUDP(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket); throw e; } } /** Private constructor with packet as byte[] parameter (typically used when receiving Packet response). * @param packet the full Packet received * @throws TransmissionError * @throws ProtocolError * @throws VersionError * @throws InternalError * @throws SizeError */ private ProtocolP2PPacketUDP(byte[] packet) throws TransmissionError, ProtocolError, VersionError, InternalError, SizeError { super(packet); constructPacket(packet); } /** Private constructor helper with packet as byte[] parameter (typically used when receiving Packet response/request). * @param packet the full Packet received * @throws TransmissionError * @throws ProtocolError * @throws VersionError * @throws InternalError * @throws SizeError */ private void constructPacket(byte[] packet) throws TransmissionError, ProtocolError, VersionError, InternalError, SizeError { // unwrap version version = packet[VERSION_POSITION]; checkProtocolVersion(); // this can throw VersionError checkCheckSum(packet); RequestResponseCode r = RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]); // this can throw ProtocolError switch (r) { case LIST_RESPONSE: payload = (Payload) new FileList(packet); break; case LOAD_RESPONSE: payload = (Payload) new FilePart(packet); break; case LOAD_REQUEST: payload = (Payload) new LoadRequest(packet); break; case HASH_REQUEST: payload = (Payload) new HashRequest(packet); break; case HASH_RESPONSE: payload = (Payload) new HashResponse(packet); break; case REGISTER: payload = (Payload) new Register(packet); break; case UNREGISTER: payload = (Payload) new Unregister(packet); break; case DISCOVER_REQUEST: payload = (Payload) new DiscoverRequest(packet); break; case DISCOVER_RESPONSE: payload = (Payload) new DiscoverResponse(packet); break; case RATIO_REQUEST: payload = (Payload) new RatioRequest(packet); break; case RATIO_RESPONSE: payload = (Payload) new RatioResponse(packet); break; case UPDATE_RATIO: payload = (Payload) new UpdateRatio(packet); break; case DENIED: payload = (Payload) new Denied(packet); break; case SIZE_REQUEST: payload = (Payload) new SizeRequest(packet); break; case SIZE_RESPONSE: payload = (Payload) new SizeResponse(packet); break; default: payload = new Payload(packet); // this can throw TransmissionError break; } } /** Private constructor helper with packet as byte[] parameter and (typically used when receiving Packet request). * @param packet the full Packet received * @param responseSocket socket address used to reception this request (use this one to respond) * @throws TransmissionError * @throws ProtocolError * @throws VersionError * @throws InternalError * @throws SizeError */ private void constructPacket(byte[] packet, DatagramSocket responseSocket) throws TransmissionError, ProtocolError, VersionError, InternalError, SizeError { constructPacket(packet); this.responseSocket = responseSocket; } /** Returns a byte[] containing full packet (typically used when sending packet). * This packet is complete and ready to be send. * @return the full packet to send * @throws InternalError */ protected byte[] toPacket() throws InternalError { byte[] packet = payload.toPacket(); packet[VERSION_POSITION] = version; setCheckSum(packet); return packet; } /** Compute checksum associated to packet. * @param packet the full packet received * @throws SizeError */ private int computeCheckSum(byte [] packet) throws SizeError { /* * The checksum field is the 16 bit one’s complement of the one’s complement sum of all 16-bit words * in the header and text. If a segment contains an odd number of header and text octets to be checksummed, * the last octet is padded on the right with zeros to form a 16-bit word for checksum purposes. * The pad is not transmitted as part of the segment. While computing the checksum, the checksum field * itself is replaced with zeros. */ int checksum = 0; for (int i=CHECKSUM_POSITION+2; i