/*
* Mojito Distributed Hash Table (Mojito DHT)
* Copyright (C) 2006-2007 LimeWire LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.limewire.mojito.io;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.mojito.KUID;
import org.limewire.mojito.handler.ResponseHandler;
import org.limewire.mojito.messages.DHTMessage;
import org.limewire.mojito.messages.FindNodeRequest;
import org.limewire.mojito.messages.FindNodeResponse;
import org.limewire.mojito.messages.FindValueRequest;
import org.limewire.mojito.messages.FindValueResponse;
import org.limewire.mojito.messages.MessageID;
import org.limewire.mojito.messages.PingRequest;
import org.limewire.mojito.messages.PingResponse;
import org.limewire.mojito.messages.RequestMessage;
import org.limewire.mojito.messages.ResponseMessage;
import org.limewire.mojito.messages.StatsRequest;
import org.limewire.mojito.messages.StatsResponse;
import org.limewire.mojito.messages.StoreRequest;
import org.limewire.mojito.messages.StoreResponse;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.util.ContactUtils;
/**
* A wrapper for outgoing DHTMessages.
* You may obtain a <code>Receipt</code> for sent Requests.
*/
public class Tag {
private static final Log LOG = LogFactory.getLog(Tag.class);
private final KUID nodeId;
private final SocketAddress dst;
private final DHTMessage message;
private ByteBuffer data;
private int size = -1;
private final ResponseHandler responseHandler;
private long sent = -1L;
private long timeout = -1L;
Tag(Contact contact, ResponseMessage message) {
this.nodeId = contact.getNodeID();
this.dst = contact.getContactAddress();
this.message = message;
this.responseHandler = null;
}
Tag(SocketAddress dst, RequestMessage message, ResponseHandler handler) {
this(null, dst, message, handler, -1L);
}
Tag(Contact contact, RequestMessage message, ResponseHandler responseHandler) {
this(contact.getNodeID(), contact.getContactAddress(), message,
responseHandler, contact.getAdaptativeTimeout());
}
Tag(KUID nodeId, SocketAddress dst, RequestMessage message,
ResponseHandler responseHandler) {
this(nodeId, dst, message, responseHandler, -1L);
}
Tag(KUID nodeId, SocketAddress dst, RequestMessage message,
ResponseHandler responseHandler, long timeout) {
this.nodeId = nodeId;
this.dst = dst;
this.message = message;
this.responseHandler = responseHandler;
this.timeout = timeout;
}
/**
* Returns true if this is a request.
*/
public boolean isRequest() {
return responseHandler != null
&& (message instanceof RequestMessage);
}
/**
* Returns the size of the serialized Message in bytes.
*/
public int getSize() {
if (size < 0) {
throw new IllegalStateException("Data is not set and the size is unknown");
}
return size;
}
/**
* Returns the MessageID.
*/
public MessageID getMessageID() {
return message.getMessageID();
}
/**
* Returns the remote Node's ID. Might be null
* if it's unknown.
*/
public KUID getNodeID() {
return nodeId;
}
/**
* Returns the remote Node's SocketAddress.
*/
public SocketAddress getSocketAddress() {
return dst;
}
/**
* Returns the DHTMessage instance.
*/
public DHTMessage getMessage() {
return message;
}
/**
* Sets the serialized message data.
*/
public void setData(ByteBuffer data) {
this.size = data.remaining();
this.data = data;
}
/**
* Returns serialized message data.
*/
public ByteBuffer getData() {
if (data == null) {
throw new IllegalStateException("Data is null");
}
return data;
}
/**
* Marks this Message as sent and returns a <code>Receipt</code>
* if this is a request.
*/
public Receipt receipt() {
sent = System.currentTimeMillis();
data = null;
return getReceipt();
}
/**
* Creates and returns a <code>Receipt</code> if this is a request.
*/
private Receipt getReceipt() throws IllegalStateException {
if (sent < 0L) {
throw new IllegalStateException("Message has not been sent yet!");
}
if (isRequest()) {
return new Receipt();
} else {
return null;
}
}
/**
* A delegate method to notify the ResponseHandler that
* an error occurred.
*/
public void handleError(IOException e) {
if (responseHandler != null) {
responseHandler.handleError(nodeId, dst, (RequestMessage)message, e);
}
}
/**
* Returns true if this Message was cancelled.
*/
public boolean isCancelled() {
if (responseHandler != null) {
return responseHandler.isCancelled();
}
return false;
}
@Override
public String toString() {
return "Tag for " + message.toString()+ " going to id "+getNodeID()+" dest: "+dst;
}
/**
* The Receipt class keeps track of requests we've sent and
* handles the response messages.
*/
public class Receipt {
private long received = -1L;
private Receipt() {
}
/**
* Returns the remote Node's ID to which this Message was
* sent or null if it's unknown (certain PingRequests).
*/
public KUID getNodeID() {
return Tag.this.getNodeID();
}
/**
* Returns the remote Node's SocketAddress to which this
* Message was sent.
*/
public SocketAddress getSocketAddress() {
return Tag.this.getSocketAddress();
}
/**
* Returns the MessageID.
*/
public MessageID getMessageID() {
return Tag.this.getMessageID();
}
/**
* Returns the RequestMessage that was sent to the remote Node.
*/
public RequestMessage getRequestMessage() {
return (RequestMessage)Tag.this.getMessage();
}
/**
* Returns the size of the sent Message.
*/
public int getSentMessageSize() {
return getSize();
}
/**
* Sets the received time marker.
*/
public void received() {
received = System.currentTimeMillis();
}
/**
* Returns the Round Trip Time (RTT).
*/
public long time() {
if (received < 0L) {
throw new IllegalStateException("The RTT is unknown as we have not received a response yet");
}
return received - sent;
}
/**
* Returns the amount of time that has elapsed since
* the request was sent.
*/
private long elapsedTime() {
return System.currentTimeMillis() - sent;
}
/**
* Returns whether or not this request has timed-out.
*/
public boolean timeout() {
long elapsed = elapsedTime();
if(timeout < 0L) {
long t = responseHandler.getTimeout();
if(LOG.isDebugEnabled()) {
LOG.debug("Default timeout: " + t + "ms for " + ContactUtils.toString(nodeId, dst));
}
return elapsed >= t;
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("Timeout: " + timeout + "ms for " + ContactUtils.toString(nodeId, dst));
}
return elapsed >= timeout;
}
}
/**
* Checks if the response is coming from the expected Node.
*/
private boolean compareNodeID(ResponseMessage response) {
if (nodeId == null) {
return (message instanceof PingRequest)
|| (message instanceof StatsRequest);
} else {
Contact node = response.getContact();
return nodeId.equals(node.getNodeID());
}
}
/**
* Checks if the response is coming from the expected Node.
*/
// This is actually not really necessary. The AddressSecurityToken in
// MessageID should take care of it.
private boolean compareAddresses(ResponseMessage response) {
Contact node = response.getContact();
InetAddress dstAddr = ((InetSocketAddress)dst).getAddress();
InetAddress srcAddr = ((InetSocketAddress)node.getContactAddress()).getAddress();
return dstAddr.equals(srcAddr);
}
/**
* Checks if the response is of the right type. That means
* it's not possible to send a PONG for a FIND_NODE request
* and so on.
*/
private boolean compareResponseType(ResponseMessage response) {
if (message instanceof PingRequest) {
return response instanceof PingResponse;
} else if (message instanceof FindNodeRequest) {
return response instanceof FindNodeResponse;
} else if (message instanceof FindValueRequest) {
return (response instanceof FindNodeResponse)
|| (response instanceof FindValueResponse);
} else if (message instanceof StoreRequest) {
return response instanceof StoreResponse;
} else if (message instanceof StatsRequest) {
return response instanceof StatsResponse;
}
return false;
}
/**
* Checks if the ResponseMessage fulfills all expected
* requirements.
*/
public boolean sanityCheck(ResponseMessage response) {
return compareNodeID(response)
&& compareAddresses(response)
&& compareResponseType(response);
}
/**
* Returns the ResponseHandler instance.
*/
public ResponseHandler getResponseHandler() {
return responseHandler;
}
/**
* A delegate method to notify the ResponseHandler that
* an error occurred.
*/
public void handleError(IOException e) {
if (responseHandler != null) {
responseHandler.handleError(nodeId, dst, (RequestMessage)message, e);
}
}
/**
* A delegate method to notify the ResponseHandler that
* a tick has passed.
*/
public void handleTick() {
if (responseHandler != null) {
responseHandler.handleTick();
}
}
/**
* Returns true if this Receipt was cancelled.
*/
public boolean isCancelled() {
return Tag.this.isCancelled();
}
}
}