|
|
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<Payload>(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress);
|
|
|
throw e;
|
|
|
} catch (ProtocolError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(ss, responseSocketAddress);
|
|
|
throw e;
|
|
|
} catch (VersionError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(new Payload(RequestResponseCode.VERSION_ERROR))).send(ss, responseSocketAddress);
|
|
|
throw e;
|
|
|
} catch (InternalError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(ss, responseSocketAddress);
|
|
|
throw e;
|
|
|
} catch (SizeError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(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 <U extends ProtocolP2PPacket<?>>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<Payload>(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket);
|
|
|
throw e;
|
|
|
} catch (ProtocolError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(new Payload(RequestResponseCode.PROTOCOL_ERROR))).send(requestSocket);
|
|
|
throw e;
|
|
|
} catch (VersionError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(new Payload(RequestResponseCode.VERSION_ERROR))).send(requestSocket);
|
|
|
throw e;
|
|
|
} catch (InternalError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(new Payload(RequestResponseCode.INTERNAL_ERROR))).send(requestSocket);
|
|
|
throw e;
|
|
|
} catch (SizeError e) {
|
|
|
(new ProtocolP2PPacketUDP<Payload>(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<CHECKSUM_POSITION+2+4+Payload.getPayloadSize(packet); ++i) {
|
|
|
checksum += BytesArrayTools.readInt16Bits(packet, i);
|
|
|
checksum &= 0xffff;
|
|
|
}
|
|
|
return checksum ^ 0xffff;
|
|
|
}
|
|
|
|
|
|
/** Used to set checksum into packet
|
|
|
* @param packet full packet
|
|
|
* @throws InternalError
|
|
|
*/
|
|
|
private void setCheckSum(byte [] packet) throws InternalError {
|
|
|
try {
|
|
|
int checksum = computeCheckSum(packet);
|
|
|
BytesArrayTools.write16Bits(packet, CHECKSUM_POSITION, checksum);
|
|
|
} catch (SizeError e) {
|
|
|
throw new InternalError();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Used to check if the checksum is correct
|
|
|
* @param packet full packet
|
|
|
* @throws TransmissionError
|
|
|
*/
|
|
|
private void checkCheckSum(byte [] packet) throws TransmissionError {
|
|
|
try {
|
|
|
int checksum = BytesArrayTools.readInt16Bits(packet, CHECKSUM_POSITION);
|
|
|
int computedCheckSum = computeCheckSum(packet);
|
|
|
if (computedCheckSum != checksum){
|
|
|
throw new TransmissionError();
|
|
|
}
|
|
|
} catch(SizeError e) {
|
|
|
throw new TransmissionError();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Get hostItem of the sender
|
|
|
* @return hostItem of the sender
|
|
|
* @throws InternalError
|
|
|
*/
|
|
|
public HostItem getHostItem() throws InternalError {
|
|
|
if (remoteHost == null) {
|
|
|
throw new InternalError();
|
|
|
}
|
|
|
return remoteHost;
|
|
|
}
|
|
|
}
|