/*
* This file is part of mlDHT.
*
* mlDHT 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.
*
* mlDHT 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 mlDHT. If not, see <http://www.gnu.org/licenses/>.
*/
package lbms.plugins.mldht.kad.messages;
import static the8472.bencode.Utils.prettyPrint;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.DHTConstants;
import lbms.plugins.mldht.kad.Key;
import lbms.plugins.mldht.kad.RPCCall;
import lbms.plugins.mldht.kad.RPCServer;
import lbms.plugins.mldht.kad.utils.AddressUtils;
import the8472.bencode.BEncoder;
/**
* Base class for all RPC messages.
*
* @author Damokles
*/
public abstract class MessageBase {
public static final String VERSION_KEY = "v";
public static final String TRANSACTION_KEY = "t";
public static final String EXTERNAL_IP_KEY = "ip";
protected byte[] mtid;
protected Method method;
protected Type type;
protected Key id;
// TODO: unify as remoteAddress
protected InetSocketAddress origin;
protected InetSocketAddress destination;
// for outgoing messages this is the IP we tell them
// for incoming messages this is the IP they told us
protected InetSocketAddress publicIP;
protected byte[] version;
protected RPCServer srv;
protected RPCCall associatedCall;
public MessageBase (byte[] mtid, Method m, Type type) {
this.mtid = mtid;
this.method = m;
this.type = type;
}
/**
* When this message arrives this function will be called upon the DHT.
* The message should then call the appropriate DHT function (double dispatch)
* @param dh_table Pointer to DHT
*/
public abstract void apply (DHT dh_table);
/**
* BEncode the message.
* @return Data array
*/
public void encode(ByteBuffer target) throws IOException
{
new BEncoder().encodeInto(getBase(),target);
}
public Map<String, Object> getBase()
{
Map<String, Object> base = new TreeMap<String, Object>();
Map<String, Object> inner = getInnerMap();
if(inner != null)
base.put(getType().innerKey(), inner);
assert(mtid != null);
// transaction ID
base.put(TRANSACTION_KEY, mtid);
// version
base.put(VERSION_KEY, DHTConstants.getVersion());
// message type
base.put(Type.TYPE_KEY, getType().getRPCTypeName());
// message method if we're a request
if(getType() == Type.REQ_MSG)
base.put(getType().getRPCTypeName(), getMethod().getRPCName());
if(publicIP != null && getType() == Type.RSP_MSG)
base.put(EXTERNAL_IP_KEY, AddressUtils.packAddress(publicIP));
return base;
}
public Map<String, Object> getInnerMap()
{
return null;
}
public void setOrigin (InetSocketAddress o) {
origin = o;
}
public InetSocketAddress getOrigin () {
return origin;
}
// where the message was sent to
public void setDestination (InetSocketAddress o) {
destination = o;
}
/// Get the origin
public InetSocketAddress getDestination () {
return destination;
}
/// Get the MTID
public byte[] getMTID () {
return mtid;
}
/// Set the MTID
public void setMTID (byte[] m) {
mtid = m;
}
public InetSocketAddress getPublicIP() {
return publicIP;
}
public void setPublicIP(InetSocketAddress publicIP) {
this.publicIP = publicIP;
}
public Optional<byte[]> getVersion () {
return Optional.ofNullable(version).map(b -> b.clone());
}
public void setVersion (byte[] version) {
this.version = version;
}
public void setServer(RPCServer srv)
{
this.srv = srv;
}
public RPCServer getServer() {
return srv;
}
public void setID(Key id) {
this.id = id;
}
/// Get the id of the sender
public Key getID () {
return id;
}
public void setAssociatedCall(RPCCall associatedCall) {
this.associatedCall = associatedCall;
}
/**
* only incoming replies have an associated call. the relation of outgoing request to call is tracked inside the call
*
* TODO: determine if that can be changed
*/
public RPCCall getAssociatedCall() {
return associatedCall;
}
/// Get the type of the message
public Type getType () {
return type;
}
/// Get the message it's method
public Method getMethod () {
return method;
}
@Override
public String toString() {
return " Method:" + method + " Type:" + type + " MessageID:" + (mtid != null ? prettyPrint(mtid) : null) + (version != null ? " version:"+prettyPrint(version) : "")+" ";
}
public static enum Type {
REQ_MSG {
@Override
String innerKey() { return "a"; }
@Override
String getRPCTypeName() { return "q"; }
}, RSP_MSG {
@Override
String innerKey() { return "r"; }
@Override
String getRPCTypeName() { return "r"; }
}, ERR_MSG {
@Override
String getRPCTypeName() { return "e"; }
@Override
String innerKey() { return "e"; }
}, INVALID;
String innerKey() {
return null;
}
String getRPCTypeName() {
return null;
}
public static final String TYPE_KEY = "y";
};
public static enum Method {
PING, FIND_NODE, GET_PEERS, ANNOUNCE_PEER, GET, PUT, UNKNOWN;
String getRPCName() {
return name().toLowerCase();
}
};
public static final Map<String, Method> messageMethod = Arrays.stream(Method.values()).filter(e -> e != Method.UNKNOWN).collect(Collectors.toMap(Method::getRPCName, Function.identity()));
}